diff --git a/.travis.yml b/.travis.yml index 81706d34..06d6183c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: false language: java jdk: oraclejdk7 -script: mvn verify -B +script: mvn clean verify -B notifications: webhooks: diff --git a/pom.xml b/pom.xml index ffb244e9..33181e69 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ fr.xephi authme - 5.2-BETA2 + 5.2-BETA3 jar AuthMeReloaded @@ -21,9 +21,9 @@ - scm:git:https://github.com/Xephi/AuthMeReloaded.git - scm:git:git@github.com:Xephi/AuthMeReloaded.git - https://github.com/Xephi/AuthMeReloaded + scm:git:https://github.com/AuthMe-Team/AuthMeReloaded.git + scm:git:git@github.com:AuthMe-Team/AuthMeReloaded.git + https://github.com/AuthMe-Team/AuthMeReloaded @@ -49,22 +49,24 @@ - - UTF-8 - ${projectEncoding} - ${projectEncoding} - 1.7 - 1.7 + + UTF-8 + 1.7 - AuthMe - ${pluginName}-${project.version} - ${project.groupId}.${project.artifactId}.${pluginName} - Xephi, sgdc3, DNx5, timvisee, games647, ljacqu - Unknown + AuthMe + CUSTOM + false + ${project.version}-b${project.buildNumber} + ${project.outputName}-${project.version} + + + ${project.outputName} + ${project.groupId}.${project.artifactId}.${bukkitplugin.name} + Xephi, sgdc3, DNx5, timvisee, games647, ljacqu - 1.9.2-R0.1-SNAPSHOT + 1.10-R0.1-SNAPSHOT @@ -77,47 +79,44 @@ - ${env.BUILD_NUMBER} + ${env.BUILD_NUMBER} + + + + skipLongHashTests + + + skipLongHashTests + + + + true - ${jarName}-noshade - src/main/java - src/test/java + + ${project.finalName}-noshade - . - false . + false LICENSE - . - true src/main/resources/ - - * - + true + src/main/resources/messages/ ./messages/ false - src/main/resources/messages/ - - *.yml - - - - src/test/resources - - @@ -126,10 +125,8 @@ maven-compiler-plugin 3.5.1 - ${jdkVersion} - ${jdkVersion} - ${testJreVersion} - ${testJreVersion} + ${project.jdkVersion} + ${project.jdkVersion} @@ -138,21 +135,30 @@ maven-surefire-plugin 2.19.1 - -Dfile.encoding=${projectEncoding} ${argLine} + -Dfile.encoding=${project.build.sourceEncoding} @{argLine} + + ${project.skipExtendedHashTests} + - + org.apache.maven.plugins maven-shade-plugin 2.4.3 false + + spigot-shade package @@ -160,6 +166,7 @@ shade + com.google.guava:guava @@ -187,15 +194,20 @@ net.ricecode.similarity fr.xephi.authme.libs.similarity + + javax.inject + fr.xephi.authme.libs.inject + org.mcstats fr.xephi.authme - target/${jarName}-spigot.jar + target/${project.finalName}-spigot.jar + legacy-shade package @@ -225,22 +237,41 @@ net.ricecode.similarity fr.xephi.authme.libs.similarity + + javax.inject + fr.xephi.authme.libs.inject + org.mcstats fr.xephi.authme - target/${jarName}-legacy.jar + target/${project.finalName}-legacy.jar + + + org.codehaus.mojo + exec-maven-plugin + 1.5.0 + + test + ${project.basedir}/target/test-classes + tools.ToolsRunner + + updateDocs + + true + + org.jacoco jacoco-maven-plugin - 0.7.6.201602180812 + 0.7.7.201606060606 prepare-agent @@ -254,7 +285,7 @@ org.eluder.coveralls coveralls-maven-plugin - 4.1.0 + 4.2.0 false @@ -264,11 +295,8 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 + 2.10.4 - UTF-8 - UTF-8 - true public false @@ -354,19 +382,6 @@ compile true - - - org.xerial - sqlite-jdbc - 3.8.11.2 - test - - - com.h2database - h2 - 1.4.191 - test - @@ -375,7 +390,6 @@ 2.0-beta9 provided - true @@ -396,7 +410,7 @@ true - + com.google.guava guava @@ -405,6 +419,15 @@ true + + + javax.inject + javax.inject + 1 + compile + true + + com.maxmind.geoip @@ -429,21 +452,12 @@ true - - - org.spigotmc - spigot-api - 1.9.2-R0.1-SNAPSHOT - provided - - - + - org.bukkit - bukkit - ${bukkitVersion} + org.spigotmc + spigot-api + ${bukkit.version} provided - true junit @@ -465,6 +479,10 @@ guava com.google.guava + + bungeecord-chat + net.md-5 + @@ -474,7 +492,6 @@ ProtocolLib 3.6.5-SNAPSHOT provided - true cglib-nodep @@ -591,14 +608,13 @@ craftbukkit - true com.onarandombox.multiversecore Multiverse-Core - 2.5 + 2.5.0-SNAPSHOT jar provided @@ -646,8 +662,15 @@ junit junit + + spigot-api + org.spigotmc + + + jettison + org.codehaus.jettison + - true @@ -666,7 +689,6 @@ craftbukkit - true @@ -741,7 +763,6 @@ org.mcstats.bukkit - true @@ -784,36 +805,6 @@ org.apache.logging.log4j - true - - - - - junit - junit - test - 4.12 - true - - - org.hamcrest - java-hamcrest - test - 2.0.0.0 - true - - - org.mockito - mockito-core - test - 2.0.5-beta - true - - - hamcrest-core - org.hamcrest - - @@ -824,5 +815,44 @@ compile true + + + + junit + junit + test + 4.12 + + + org.hamcrest + java-hamcrest + test + 2.0.0.0 + + + org.mockito + mockito-core + test + 2.0.5-beta + + + hamcrest-core + org.hamcrest + + + + + + org.xerial + sqlite-jdbc + 3.8.11.2 + test + + + com.h2database + h2 + 1.4.192 + test + diff --git a/samples/website_integration/AuthMeController.php b/samples/website_integration/AuthMeController.php new file mode 100644 index 00000000..d1e94ddf --- /dev/null +++ b/samples/website_integration/AuthMeController.php @@ -0,0 +1,126 @@ +getHashFromDatabase($username); + if ($hash) { + return $this->isValidPassword($password, $hash); + } + } + return false; + } + + /** + * Returns whether the user exists in the database or not. + * + * @param string $username the username to check + * @return bool true if the user exists; false otherwise + */ + function isUserRegistered($username) { + $mysqli = $this->getAuthmeMySqli(); + if ($mysqli !== null) { + $stmt = $mysqli->prepare('SELECT 1 FROM ' . self::AUTHME_TABLE . ' WHERE username = ?'); + $stmt->bind_param('s', $username); + $stmt->execute(); + return $stmt->fetch(); + } + + // Defensive default to true; we actually don't know + return true; + } + + /** + * Registers a player with the given username. + * + * @param string $username the username to register + * @param string $password the password to associate to the user + * @return bool whether or not the registration was successful + */ + function register($username, $password) { + $mysqli = $this->getAuthmeMySqli(); + if ($mysqli !== null) { + $hash = $this->hash($password); + $stmt = $mysqli->prepare('INSERT INTO ' . self::AUTHME_TABLE . ' (username, realname, password, ip) ' + . 'VALUES (?, ?, ?, ?)'); + $username_low = strtolower($username); + $stmt->bind_param('ssss', $username, $username_low, $hash, $_SERVER['REMOTE_ADDR']); + return $stmt->execute(); + } + return false; + } + + /** + * Hashes the given password. + * + * @param $password string the clear-text password to hash + * @return string the resulting hash + */ + protected abstract function hash($password); + + /** + * Checks whether the given password matches the hash. + * + * @param $password string the clear-text password + * @param $hash string the password hash + * @return boolean true if the password matches, false otherwise + */ + protected abstract function isValidPassword($password, $hash); + + /** + * Returns a connection to the database. + * + * @return mysqli|null the mysqli object or null upon error + */ + private function getAuthmeMySqli() { + // CHANGE YOUR DATABASE DETAILS HERE BELOW: host, user, password, database name + $mysqli = new mysqli('localhost', 'root', '', 'authme'); + if (mysqli_connect_error()) { + printf('Could not connect to AuthMe database. Errno: %d, error: "%s"', + mysqli_connect_errno(), mysqli_connect_error()); + return null; + } + return $mysqli; + } + + /** + * Retrieves the hash associated with the given user from the database. + * + * @param string $username the username whose hash should be retrieved + * @return string|null the hash, or null if unavailable (e.g. username doesn't exist) + */ + private function getHashFromDatabase($username) { + // Add here your database host, username, password and database name + $mysqli = $this->getAuthmeMySqli(); + if ($mysqli !== null) { + $stmt = $mysqli->prepare('SELECT password FROM ' . self::AUTHME_TABLE . ' WHERE username = ?'); + $stmt->bind_param('s', $username); + $stmt->execute(); + $stmt->bind_result($password); + if ($stmt->fetch()) { + return $password; + } + } + return null; + } + +} \ No newline at end of file diff --git a/samples/website_integration/Bcrypt.php b/samples/website_integration/Bcrypt.php new file mode 100644 index 00000000..225baf80 --- /dev/null +++ b/samples/website_integration/Bcrypt.php @@ -0,0 +1,20 @@ +CHARS = self::initRandomChars(); + } + + protected function isValidPassword($password, $hash) { + // $SHA$salt$hash, where hash := sha256(sha256(password) . salt) + $parts = explode('$', $hash); + return count($parts) === 4 && $parts[3] === hash('sha256', hash('sha256', $password) . $parts[2]); + } + + protected function hash($password) { + $salt = $this->generateSalt(); + return '$SHA$' . $salt . '$' . hash('sha256', hash('sha256', $password) . $salt); + } + + /** + * @return string randomly generated salt + */ + private function generateSalt() { + $maxCharIndex = count($this->CHARS) - 1; + $salt = ''; + for ($i = 0; $i < self::SALT_LENGTH; ++$i) { + $salt .= $this->CHARS[mt_rand(0, $maxCharIndex)]; + } + return $salt; + } + + private static function initRandomChars() { + return array_merge(range('0', '9'), range('a', 'f')); + } + +} diff --git a/samples/website_integration/bcrypt/integration.php b/samples/website_integration/bcrypt/integration.php deleted file mode 100644 index 75911838..00000000 --- a/samples/website_integration/bcrypt/integration.php +++ /dev/null @@ -1,107 +0,0 @@ -prepare('SELECT password FROM ' . AUTHME_TABLE . ' WHERE username = ?'); - $stmt->bind_param('s', $username); - $stmt->execute(); - $stmt->bind_result($password); - if ($stmt->fetch()) { - return $password; - } - } - return null; -} - -/** - * Returns whether the user exists in the database or not. - * - * @param string $username the username to check - * @return bool true if the user exists; false otherwise - */ -function authme_has_user($username) { - $mysqli = authme_get_mysqli(); - if ($mysqli !== null) { - $stmt = $mysqli->prepare('SELECT 1 FROM ' . AUTHME_TABLE . ' WHERE username = ?'); - $stmt->bind_param('s', $username); - $stmt->execute(); - return $stmt->fetch(); - } - - // Defensive default to true; we actually don't know - return true; -} - -/** - * Registers a player with the given username. - * - * @param string $username the username to register - * @param string $password the password to associate to the user - * @return bool whether or not the registration was successful - */ -function authme_register($username, $password) { - $mysqli = authme_get_mysqli(); - if ($mysqli !== null) { - $hash = password_hash($password, PASSWORD_BCRYPT); - $stmt = $mysqli->prepare('INSERT INTO ' . AUTHME_TABLE . ' (username, realname, password, ip) ' - . 'VALUES (?, ?, ?, ?)'); - $username_low = strtolower($username); - $stmt->bind_param('ssss', $username, $username_low, $hash, $_SERVER['REMOTE_ADDR']); - return $stmt->execute(); - } - return false; -} - diff --git a/samples/website_integration/bcrypt/form.php b/samples/website_integration/index.php similarity index 66% rename from samples/website_integration/bcrypt/form.php rename to samples/website_integration/index.php index 7801be4d..0c2a1a1c 100644 --- a/samples/website_integration/bcrypt/form.php +++ b/samples/website_integration/index.php @@ -1,6 +1,6 @@ @@ -12,17 +12,23 @@ checkPassword($user, $pass)) { printf('

Hello, %s!

', htmlspecialchars($user)); echo 'Successful login. Nice to have you back!' - . '
Back to form'; + . '
Back to form'; return true; } else { echo '

Error

Invalid username or password.'; @@ -63,15 +69,15 @@ function process_login($user, $pass) { } // Register logic -function process_register($user, $pass) { - if (authme_has_user($user)) { +function process_register($user, $pass, AuthMeController $controller) { + if ($controller->isUserRegistered($user)) { echo '

Error

This user already exists.'; } else { // Note that we don't validate the password or username at all in this demo... - $register_success = authme_register($user, $pass); + $register_success = $controller->register($user, $pass); if ($register_success) { printf('

Welcome, %s!

Thanks for registering', htmlspecialchars($user)); - echo '
Back to form'; + echo '
Back to form'; return true; } else { echo '

Error

Unfortunately, there was an error during the registration.'; diff --git a/samples/website_integration/sha256/form.php b/samples/website_integration/sha256/form.php deleted file mode 100644 index 5ffecf34..00000000 --- a/samples/website_integration/sha256/form.php +++ /dev/null @@ -1,52 +0,0 @@ - - - - - AuthMe Integration Sample - - - -Hello, %s!', htmlspecialchars($user)); - echo 'Successful login. Nice to have you back!' - . '
Back to form'; - $was_successful = true; - } else { - echo '

Error

Invalid username or password.'; - } -} - -if (!$was_successful) { - echo '

Login sample

-This is a demo form for AuthMe website integration. Enter your AuthMe login details -into the following form to test it. -
- - - - -
Name
Pass
-
'; -} - -function get_from_post_or_empty($index_name) { - return trim( - filter_input(INPUT_POST, $index_name, FILTER_UNSAFE_RAW, FILTER_REQUIRE_SCALAR | FILTER_FLAG_STRIP_LOW) - ?: ''); -} -?> - - - diff --git a/samples/website_integration/sha256/integration.php b/samples/website_integration/sha256/integration.php deleted file mode 100644 index e0de0bb1..00000000 --- a/samples/website_integration/sha256/integration.php +++ /dev/null @@ -1,67 +0,0 @@ -prepare("SELECT password FROM $authme_table WHERE username = ?"); - $stmt->bind_param('s', $username); - $stmt->execute(); - $stmt->bind_result($password); - if ($stmt->fetch()) { - return $password; - } - } - return null; -} - -/** - * Checks the given clear-text password against the hash. - * - * @param string $password the clear-text password to check - * @param string $hash the hash to check the password against - * @return bool true iff the password matches the hash, false otherwise - */ -function authme_check_hash($password, $hash) { - // $SHA$salt$hash, where hash := sha256(sha256(password) . salt) - $parts = explode('$', $hash); - return count($parts) === 4 - && $parts[3] === hash('sha256', hash('sha256', $password) . $parts[2]); -} diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java index 75a2a3b5..d85cc784 100644 --- a/src/main/java/fr/xephi/authme/AntiBot.java +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -9,8 +9,8 @@ import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.util.BukkitService; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; +import javax.inject.Inject; +import java.util.concurrent.CopyOnWriteArrayList; import static fr.xephi.authme.util.BukkitService.TICKS_PER_MINUTE; import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; @@ -24,11 +24,13 @@ public class AntiBot { private final Messages messages; private final PermissionsManager permissionsManager; private final BukkitService bukkitService; - private final List antibotPlayers = new ArrayList<>(); + public final CopyOnWriteArrayList antibotKicked = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList antibotPlayers = new CopyOnWriteArrayList(); private AntiBotStatus antiBotStatus = AntiBotStatus.DISABLED; - public AntiBot(NewSetting settings, Messages messages, PermissionsManager permissionsManager, - BukkitService bukkitService) { + @Inject + AntiBot(NewSetting settings, Messages messages, PermissionsManager permissionsManager, + BukkitService bukkitService) { this.settings = settings; this.messages = messages; this.permissionsManager = permissionsManager; @@ -75,6 +77,7 @@ public class AntiBot { if (antiBotStatus == AntiBotStatus.ACTIVE) { antiBotStatus = AntiBotStatus.LISTENING; antibotPlayers.clear(); + antibotKicked.clear(); for (String s : messages.retrieve(MessageKey.ANTIBOT_AUTO_DISABLED_MESSAGE)) { bukkitService.broadcastMessage(s.replace("%m", Integer.toString(duration))); } @@ -83,7 +86,12 @@ public class AntiBot { }, duration * TICKS_PER_MINUTE); } - public void checkAntiBot(final Player player) { + /** + * Handles a player joining the server and checks if AntiBot needs to be activated. + * + * @param player the player who joined the server + */ + public void handlePlayerJoin(final Player player) { if (antiBotStatus == AntiBotStatus.ACTIVE || antiBotStatus == AntiBotStatus.DISABLED) { return; } diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 66a49dbd..0fb32ddb 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -1,32 +1,5 @@ package fr.xephi.authme; -import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_ACCOUNT; -import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD; -import static fr.xephi.authme.settings.properties.EmailSettings.RECALL_PLAYERS; -import static fr.xephi.authme.settings.properties.PluginSettings.HELP_HEADER; - -import java.io.File; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - -import org.apache.logging.log4j.LogManager; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; - import fr.xephi.authme.api.API; import fr.xephi.authme.api.NewAPI; import fr.xephi.authme.cache.auth.PlayerAuth; @@ -34,12 +7,7 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; -import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandHandler; -import fr.xephi.authme.command.CommandInitializer; -import fr.xephi.authme.command.CommandMapper; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.help.HelpProvider; import fr.xephi.authme.datasource.CacheDataSource; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; @@ -48,13 +16,15 @@ import fr.xephi.authme.datasource.MySQL; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.hooks.BungeeCordMessage; import fr.xephi.authme.hooks.PluginHooks; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.MetricsStarter; import fr.xephi.authme.listener.AuthMeBlockListener; import fr.xephi.authme.listener.AuthMeEntityListener; import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.listener.AuthMePlayerListener16; import fr.xephi.authme.listener.AuthMePlayerListener18; -import fr.xephi.authme.listener.AuthMePlayerListener19; import fr.xephi.authme.listener.AuthMeServerListener; import fr.xephi.authme.listener.AuthMeTabCompletePacketAdapter; import fr.xephi.authme.listener.AuthMeTablistPacketAdapter; @@ -64,9 +34,7 @@ import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.Management; -import fr.xephi.authme.process.ProcessService; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.SHA256; import fr.xephi.authme.settings.NewSetting; @@ -82,6 +50,7 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.settings.properties.SettingsFieldRetriever; import fr.xephi.authme.settings.propertymap.PropertyMap; +import fr.xephi.authme.task.PurgeTask; import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.FileUtils; @@ -89,7 +58,32 @@ import fr.xephi.authme.util.GeoLiteAPI; import fr.xephi.authme.util.MigrationService; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.ValidationService; +import org.apache.logging.log4j.LogManager; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + +import java.io.File; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_ACCOUNT; +import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD; +import static fr.xephi.authme.settings.properties.EmailSettings.RECALL_PLAYERS; /** * The AuthMe main class. @@ -105,17 +99,13 @@ public class AuthMe extends JavaPlugin { // Private Instances private static AuthMe plugin; - private static Server server; /* * Maps and stuff */ - // TODO #601: Integrate CaptchaManager public final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); - public final ConcurrentHashMap captcha = new ConcurrentHashMap<>(); - public final ConcurrentHashMap cap = new ConcurrentHashMap<>(); /* - * Public Instances + * Public instances */ public NewAPI api; // TODO #655: Encapsulate mail @@ -124,7 +114,6 @@ public class AuthMe extends JavaPlugin { public DataManager dataManager; /* * Private instances - * TODO #432: Move instantiation and management of these services */ // TODO #604: Encapsulate ProtocolLib members public AuthMeInventoryPacketAdapter inventoryProtector; @@ -140,15 +129,16 @@ public class AuthMe extends JavaPlugin { private DataSource database; private PluginHooks pluginHooks; private SpawnLoader spawnLoader; - private AntiBot antiBot; private boolean autoPurging; private BukkitService bukkitService; + private AuthMeServiceInitializer initializer; /** * Get the plugin's instance. * * @return AuthMe */ + @Deprecated public static AuthMe getInstance() { return plugin; } @@ -180,24 +170,6 @@ public class AuthMe extends JavaPlugin { return pluginBuildNumber; } - /** - * Get the Messages instance. - * - * @return Plugin's messages. - */ - public Messages getMessages() { - return messages; - } - - /** - * Get the plugin's NewSetting instance. - * - * @return NewSetting. - */ - public NewSetting getSettings() { - return newSettings; - } - // Get version and build number of the plugin private void setPluginInfos() { String versionRaw = this.getDescription().getVersion(); @@ -217,25 +189,23 @@ public class AuthMe extends JavaPlugin { @Override public void onEnable() { // Set various instances - server = getServer(); plugin = this; ConsoleLogger.setLogger(getLogger()); - setPluginInfos(); // Load settings and custom configurations, if it fails, stop the server due to security reasons. newSettings = createNewSetting(); if (newSettings == null) { - ConsoleLogger.showError("Could not load configuration. Aborting."); - server.shutdown(); + getLogger().warning("Could not load configuration. Aborting."); + getServer().shutdown(); return; } - ConsoleLogger.setLoggingOptions(newSettings.getProperty(SecuritySettings.USE_LOGGING), - new File(getDataFolder(), "authme.log")); + ConsoleLogger.setLogFile(new File(getDataFolder(), "authme.log")); + ConsoleLogger.setLoggingOptions(newSettings); // Old settings manager if (!loadSettings()) { - server.shutdown(); + getServer().shutdown(); setEnabled(false); return; } @@ -251,23 +221,43 @@ public class AuthMe extends JavaPlugin { stopOrUnload(); return; } - - bukkitService = new BukkitService(this); - pluginHooks = new PluginHooks(server.getPluginManager()); - MigrationService.changePlainTextToSha256(newSettings, database, new SHA256()); - passwordSecurity = new PasswordSecurity(getDataSource(), newSettings, Bukkit.getPluginManager()); - // Initialize spawn loader - spawnLoader = new SpawnLoader(getDataFolder(), newSettings, pluginHooks); - permsMan = initializePermissionsManager(); - antiBot = new AntiBot(newSettings, messages, permsMan, bukkitService); - ValidationService validationService = new ValidationService(newSettings, database, permsMan); - commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity, newSettings, - pluginHooks, spawnLoader, antiBot, validationService, bukkitService); + + initializer = new AuthMeServiceInitializer("fr.xephi.authme"); + // Register elements of the Bukkit / JavaPlugin environment + initializer.register(AuthMe.class, this); + initializer.register(Server.class, getServer()); + initializer.register(PluginManager.class, getServer().getPluginManager()); + initializer.register(BukkitScheduler.class, getServer().getScheduler()); + initializer.provide(DataFolder.class, getDataFolder()); + + // Register elements we instantiate manually + initializer.register(NewSetting.class, newSettings); + initializer.register(Messages.class, messages); + initializer.register(DataSource.class, database); + + // Some statically injected things + initializer.register(PlayerCache.class, PlayerCache.getInstance()); + + // Note ljacqu 20160612: Instantiate LimboCache first to make sure it is instantiated + // (because sometimes it's used via LimboCache.getInstance()) + // Once LimboCache#getInstance() no longer exists this can be removed! + initializer.get(LimboCache.class); + + permsMan = initializer.get(PermissionsManager.class); + bukkitService = initializer.get(BukkitService.class); + pluginHooks = initializer.get(PluginHooks.class); + passwordSecurity = initializer.get(PasswordSecurity.class); + spawnLoader = initializer.get(SpawnLoader.class); + commandHandler = initializer.get(CommandHandler.class); + api = initializer.get(NewAPI.class); + management = initializer.get(Management.class); + dataManager = initializer.get(DataManager.class); + initializer.get(API.class); // Set up Metrics - MetricsStarter.setupMetrics(plugin, newSettings); + MetricsStarter.setupMetrics(this, newSettings); // Set console filter setupConsoleFilter(); @@ -284,32 +274,21 @@ public class AuthMe extends JavaPlugin { // End of Hooks // Do a backup on start - new PerformBackup(plugin, newSettings).doBackup(PerformBackup.BackupCause.START); + new PerformBackup(this, newSettings).doBackup(PerformBackup.BackupCause.START); // Setup the inventory backup playerBackup = new JsonCache(); - // Set the DataManager - dataManager = new DataManager(this, pluginHooks, bukkitService); - - // Set up the new API - setupApi(); - - // Set up the management - ProcessService processService = new ProcessService(newSettings, messages, this, database, - passwordSecurity, pluginHooks, spawnLoader, validationService, bukkitService); - management = new Management(this, processService, database, PlayerCache.getInstance()); // Set up the BungeeCord hook - setupBungeeCordHook(newSettings); + setupBungeeCordHook(newSettings, initializer); // Reload support hook reloadSupportHook(); // Register event listeners - registerEventListeners( - messages, database, management, pluginHooks, spawnLoader, antiBot, bukkitService, validationService); + registerEventListeners(initializer); // Start Email recall task if needed scheduleRecallEmailTask(); @@ -327,23 +306,6 @@ public class AuthMe extends JavaPlugin { runAutoPurge(); } - /** - * Reload certain components. - * - * @throws Exception if an error occurs - */ - public void reload() throws Exception { - newSettings.reload(); - // We do not change database type for consistency issues, but we'll output a note in the logs - if (!newSettings.getProperty(DatabaseSettings.BACKEND).equals(database.getType())) { - ConsoleLogger.info("Note: cannot change database type during /authme reload"); - } - database.reload(); - messages.reload(newSettings.getMessagesFile()); - passwordSecurity.reload(); - spawnLoader.initialize(newSettings); - } - /** * Set up the mail API, if enabled. */ @@ -373,38 +335,27 @@ public class AuthMe extends JavaPlugin { /** * Register all event listeners. */ - private void registerEventListeners(Messages messages, DataSource dataSource, Management management, - PluginHooks pluginHooks, SpawnLoader spawnLoader, AntiBot antiBot, - BukkitService bukkitService, ValidationService validationService) { + private void registerEventListeners(AuthMeServiceInitializer initializer) { // Get the plugin manager instance - PluginManager pluginManager = server.getPluginManager(); + PluginManager pluginManager = getServer().getPluginManager(); // Register event listeners - pluginManager.registerEvents(new AuthMePlayerListener( - this, newSettings, messages, dataSource, antiBot, management, bukkitService, validationService), this); - pluginManager.registerEvents(new AuthMeBlockListener(), this); - pluginManager.registerEvents(new AuthMeEntityListener(), this); - pluginManager.registerEvents(new AuthMeServerListener( - this, messages, newSettings, pluginHooks, spawnLoader, validationService), this); + pluginManager.registerEvents(initializer.get(AuthMePlayerListener.class), this); + pluginManager.registerEvents(initializer.get(AuthMeBlockListener.class), this); + pluginManager.registerEvents(initializer.get(AuthMeEntityListener.class), this); + pluginManager.registerEvents(initializer.get(AuthMeServerListener.class), this); // Try to register 1.6 player listeners try { Class.forName("org.bukkit.event.player.PlayerEditBookEvent"); - pluginManager.registerEvents(new AuthMePlayerListener16(), this); + pluginManager.registerEvents(initializer.get(AuthMePlayerListener16.class), this); } catch (ClassNotFoundException ignore) { } // Try to register 1.8 player listeners try { Class.forName("org.bukkit.event.player.PlayerInteractAtEntityEvent"); - pluginManager.registerEvents(new AuthMePlayerListener18(), this); - } catch (ClassNotFoundException ignore) { - } - - // Try to register 1.9 player listeners - try { - Class.forName("org.spigotmc.event.player.PlayerSpawnLocationEvent"); - pluginManager.registerEvents(new AuthMePlayerListener19(this), this); + pluginManager.registerEvents(initializer.get(AuthMePlayerListener18.class), this); } catch (ClassNotFoundException ignore) { } } @@ -430,38 +381,14 @@ public class AuthMe extends JavaPlugin { /** * Set up the BungeeCord hook. */ - private void setupBungeeCordHook(NewSetting settings) { + private void setupBungeeCordHook(NewSetting settings, AuthMeServiceInitializer initializer) { if (settings.getProperty(HooksSettings.BUNGEECORD)) { Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); Bukkit.getMessenger().registerIncomingPluginChannel( - this, "BungeeCord", new BungeeCordMessage(this)); + this, "BungeeCord", initializer.get(BungeeCordMessage.class)); } } - private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages, - PasswordSecurity passwordSecurity, NewSetting settings, - PluginHooks pluginHooks, SpawnLoader spawnLoader, AntiBot antiBot, - ValidationService validationService, BukkitService bukkitService) { - HelpProvider helpProvider = new HelpProvider(permissionsManager, settings.getProperty(HELP_HEADER)); - Set baseCommands = CommandInitializer.buildCommands(); - CommandMapper mapper = new CommandMapper(baseCommands, permissionsManager); - CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity, - permissionsManager, settings, pluginHooks, spawnLoader, antiBot, validationService, bukkitService); - return new CommandHandler(commandService); - } - - /** - * Set up the API. This sets up the new and the old API. - */ - @SuppressWarnings("deprecation") - private void setupApi() { - // Set up the API - api = new NewAPI(this); - - // Set up the deprecated API - new API(this); - } - /** * Load the plugin's settings. * @@ -474,7 +401,7 @@ public class AuthMe extends JavaPlugin { } catch (Exception e) { ConsoleLogger.logException("Can't load the configuration file... Something went wrong. " + "To avoid security issues the server will shut down!", e); - server.shutdown(); + getServer().shutdown(); } return false; } @@ -492,7 +419,7 @@ public class AuthMe extends JavaPlugin { * Set up the console filter. */ private void setupConsoleFilter() { - if (Settings.removePassword) { + if (newSettings.getProperty(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE)) { ConsoleFilter filter = new ConsoleFilter(); getLogger().setFilter(filter); Bukkit.getLogger().setFilter(filter); @@ -510,33 +437,37 @@ public class AuthMe extends JavaPlugin { @Override public void onDisable() { // Save player data - if (bukkitService != null) { + BukkitService bukkitService = initializer.getIfAvailable(BukkitService.class); + LimboCache limboCache = initializer.getIfAvailable(LimboCache.class); + + if (bukkitService != null && limboCache != null) { Collection players = bukkitService.getOnlinePlayers(); for (Player player : players) { - savePlayer(player); + savePlayer(player, limboCache); } } // Do backup on stop if enabled if (newSettings != null) { - new PerformBackup(plugin, newSettings).doBackup(PerformBackup.BackupCause.STOP); + new PerformBackup(this, newSettings).doBackup(PerformBackup.BackupCause.STOP); } + final AuthMe pluginInstance = this; new Thread(new Runnable() { @Override public void run() { List pendingTasks = new ArrayList<>(); for (BukkitTask pendingTask : getServer().getScheduler().getPendingTasks()) { - if (pendingTask.getOwner().equals(plugin) && !pendingTask.isSync()) { + if (pendingTask.getOwner().equals(pluginInstance) && !pendingTask.isSync()) { pendingTasks.add(pendingTask.getTaskId()); } } - ConsoleLogger.info("Waiting for " + pendingTasks.size() + " tasks to finish"); + getLogger().info("Waiting for " + pendingTasks.size() + " tasks to finish"); int progress = 0; for (int taskId : pendingTasks) { int maxTries = 5; while (getServer().getScheduler().isCurrentlyRunning(taskId)) { if (maxTries <= 0) { - ConsoleLogger.info("Async task " + taskId + " times out after to many tries"); + getLogger().info("Async task " + taskId + " times out after to many tries"); break; } try { @@ -547,7 +478,7 @@ public class AuthMe extends JavaPlugin { } progress++; - ConsoleLogger.info("Progress: " + progress + " / " + pendingTasks.size()); + getLogger().info("Progress: " + progress + " / " + pendingTasks.size()); } if (database != null) { database.close(); @@ -555,19 +486,18 @@ public class AuthMe extends JavaPlugin { } }, "AuthMe-DataSource#close").start(); - // Close the database - // Disabled correctly ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " disabled!"); + ConsoleLogger.close(); } // Stop/unload the server/plugin as defined in the configuration public void stopOrUnload() { if (Settings.isStopEnabled) { ConsoleLogger.showError("THE SERVER IS GOING TO SHUT DOWN AS DEFINED IN THE CONFIGURATION!"); - server.shutdown(); + getServer().shutdown(); } else { - server.getPluginManager().disablePlugin(AuthMe.getInstance()); + getServer().getPluginManager().disablePlugin(AuthMe.getInstance()); } } @@ -610,7 +540,7 @@ public class AuthMe extends JavaPlugin { database = dataSource; if (DataSourceType.SQLITE == dataSourceType) { - server.getScheduler().runTaskAsynchronously(this, new Runnable() { + getServer().getScheduler().runTaskAsynchronously(this, new Runnable() { @Override public void run() { int accounts = database.getAccountsRegistered(); @@ -623,15 +553,6 @@ public class AuthMe extends JavaPlugin { } } - /** - * Set up the permissions manager. - */ - private PermissionsManager initializePermissionsManager() { - PermissionsManager manager = new PermissionsManager(Bukkit.getServer(), getLogger()); - manager.setup(); - return manager; - } - // Set the console filter to remove the passwords private void setLog4JFilter() { Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { @@ -648,7 +569,7 @@ public class AuthMe extends JavaPlugin { // Check the presence of the ProtocolLib plugin public void checkProtocolLib() { - if (!server.getPluginManager().isPluginEnabled("ProtocolLib")) { + if (!getServer().getPluginManager().isPluginEnabled("ProtocolLib")) { if (newSettings.getProperty(RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN)) { ConsoleLogger.showError("WARNING! The protectInventory feature requires ProtocolLib! Disabling it..."); Settings.protectInventoryBeforeLogInEnabled = false; @@ -682,8 +603,8 @@ public class AuthMe extends JavaPlugin { } // Save Player Data - private void savePlayer(Player player) { - if (Utils.isNPC(player) || Utils.isUnrestricted(player)) { + private void savePlayer(Player player, LimboCache limboCache) { + if (safeIsNpc(player) || Utils.isUnrestricted(player)) { return; } String name = player.getName().toLowerCase(); @@ -694,8 +615,8 @@ public class AuthMe extends JavaPlugin { .location(player.getLocation()).build(); database.updateQuitLoc(auth); } - if (LimboCache.getInstance().hasLimboPlayer(name)) { - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); + if (limboCache.hasLimboPlayer(name)) { + LimboPlayer limbo = limboCache.getLimboPlayer(name); if (!Settings.noTeleport) { player.teleport(limbo.getLoc()); } @@ -703,7 +624,7 @@ public class AuthMe extends JavaPlugin { Utils.addNormal(player, limbo.getGroup()); player.setOp(limbo.isOperator()); limbo.getTimeoutTask().cancel(); - LimboCache.getInstance().deleteLimboPlayer(name); + limboCache.deleteLimboPlayer(name); if (this.playerBackup.doesCacheExist(player)) { this.playerBackup.removeCache(player); } @@ -711,14 +632,8 @@ public class AuthMe extends JavaPlugin { PlayerCache.getInstance().removePlayer(name); } - // Select the player to kick when a vip player joins the server when full - public Player generateKickPlayer(Collection collection) { - for (Player player : collection) { - if (!getPermissionsManager().hasPermission(player, PlayerStatePermission.IS_VIP)) { - return player; - } - } - return null; + private boolean safeIsNpc(Player player) { + return pluginHooks != null && pluginHooks.isNpc(player) || player.hasMetadata("NPC"); } // Purge inactive players from the database, as defined in the configuration @@ -726,33 +641,22 @@ public class AuthMe extends JavaPlugin { if (!newSettings.getProperty(PurgeSettings.USE_AUTO_PURGE) || autoPurging) { return; } + autoPurging = true; - server.getScheduler().runTaskAsynchronously(this, new Runnable() { - @Override - public void run() { - ConsoleLogger.info("AutoPurging the Database..."); - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.DATE, -newSettings.getProperty(PurgeSettings.DAYS_BEFORE_REMOVE_PLAYER)); - long until = calendar.getTimeInMillis(); - List cleared = database.autoPurgeDatabase(until); - if (CollectionUtils.isEmpty(cleared)) { - return; - } - ConsoleLogger.info("AutoPurging the Database: " + cleared.size() + " accounts removed!"); - if (newSettings.getProperty(PurgeSettings.REMOVE_ESSENTIALS_FILES) && pluginHooks.isEssentialsAvailable()) - dataManager.purgeEssentials(cleared); - if (newSettings.getProperty(PurgeSettings.REMOVE_PLAYER_DAT)) - dataManager.purgeDat(cleared); - if (newSettings.getProperty(PurgeSettings.REMOVE_LIMITED_CREATIVE_INVENTORIES)) - dataManager.purgeLimitedCreative(cleared); - if (newSettings.getProperty(PurgeSettings.REMOVE_ANTI_XRAY_FILE)) - dataManager.purgeAntiXray(cleared); - if (newSettings.getProperty(PurgeSettings.REMOVE_PERMISSIONS)) - dataManager.purgePermissions(cleared); - ConsoleLogger.info("AutoPurge Finished!"); - autoPurging = false; - } - }); + + ConsoleLogger.info("AutoPurging the Database..."); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, -newSettings.getProperty(PurgeSettings.DAYS_BEFORE_REMOVE_PLAYER)); + long until = calendar.getTimeInMillis(); + Set cleared = database.autoPurgeDatabase(until); + if (CollectionUtils.isEmpty(cleared)) { + return; + } + + ConsoleLogger.info("AutoPurging the Database: " + cleared.size() + " accounts removed!"); + ConsoleLogger.info("Purging user accounts..."); + new PurgeTask(plugin, Bukkit.getConsoleSender(), cleared, true, Bukkit.getOfflinePlayers()) + .runTaskTimer(plugin, 0, 1); } // Return the spawn location of a player @@ -784,6 +688,7 @@ public class AuthMe extends JavaPlugin { public String replaceAllInfo(String message, Player player) { String playersOnline = Integer.toString(bukkitService.getOnlinePlayers().size()); String ipAddress = Utils.getPlayerIp(player); + Server server = getServer(); return message .replace("&", "\u00a7") .replace("{PLAYER}", player.getName()) @@ -797,17 +702,7 @@ public class AuthMe extends JavaPlugin { .replace("{COUNTRY}", GeoLiteAPI.getCountryName(ipAddress)); } - public boolean isLoggedIp(String name, String ip) { - int count = 0; - for (Player player : bukkitService.getOnlinePlayers()) { - if (ip.equalsIgnoreCase(Utils.getPlayerIp(player)) - && database.isLogged(player.getName().toLowerCase()) - && !player.getName().equalsIgnoreCase(name)) { - ++count; - } - } - return count >= Settings.getMaxLoginPerIp; - } + /** * Handle Bukkit commands. @@ -832,34 +727,66 @@ public class AuthMe extends JavaPlugin { return commandHandler.processCommand(sender, commandLabel, args); } + public void notifyAutoPurgeEnd() { + this.autoPurging = false; + } + + + // ------------- + // Service getters (deprecated) + // Use @Inject fields instead + // ------------- /** - * Get the permissions manager instance. - * - * @return Permissions Manager instance. + * @return NewSetting + * @deprecated should be used in API classes only (temporarily) */ + @Deprecated + public NewSetting getSettings() { + return newSettings; + } + + /** + * @return permission manager + * @deprecated should be used in API classes only (temporarily) + */ + @Deprecated public PermissionsManager getPermissionsManager() { return this.permsMan; } /** - * Return the management instance. - * - * @return management The Management + * @return process manager + * @deprecated should be used in API classes only (temporarily) */ + @Deprecated public Management getManagement() { return management; } + /** + * @return the datasource + * @deprecated should be used in API classes only (temporarily) + */ + @Deprecated public DataSource getDataSource() { return database; } + /** + * @return password manager + * @deprecated should be used in API classes only (temporarily) + */ + @Deprecated public PasswordSecurity getPasswordSecurity() { return passwordSecurity; } + /** + * @return plugin hooks + * @deprecated should be used in API classes only (temporarily) + */ + @Deprecated public PluginHooks getPluginHooks() { return pluginHooks; } - } diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java index eca20fbb..284a7442 100644 --- a/src/main/java/fr/xephi/authme/ConsoleLogger.java +++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java @@ -1,15 +1,17 @@ package fr.xephi.authme; import com.google.common.base.Throwables; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.StringUtils; import java.io.File; +import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -20,22 +22,38 @@ public final class ConsoleLogger { private static final String NEW_LINE = System.getProperty("line.separator"); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("[MM-dd HH:mm:ss]"); private static Logger logger; + private static boolean enableDebug = false; private static boolean useLogging = false; private static File logFile; + private static FileWriter fileWriter; private ConsoleLogger() { - // Service class } public static void setLogger(Logger logger) { ConsoleLogger.logger = logger; } - public static void setLoggingOptions(boolean useLogging, File logFile) { - ConsoleLogger.useLogging = useLogging; + public static void setLogFile(File logFile) { ConsoleLogger.logFile = logFile; } + public static void setLoggingOptions(NewSetting settings) { + ConsoleLogger.useLogging = settings.getProperty(SecuritySettings.USE_LOGGING); + ConsoleLogger.enableDebug = !settings.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE); + if (useLogging) { + if (fileWriter == null) { + try { + fileWriter = new FileWriter(logFile, true); + } catch (IOException e) { + ConsoleLogger.logException("Failed to create the log file:", e); + } + } + } else { + close(); + } + } + /** * Print an info message. * @@ -46,6 +64,20 @@ public final class ConsoleLogger { if (useLogging) { writeLog(message); } + + } + + public static void debug(String message) { + if (enableDebug) { + //creating and filling an exception is a expensive call + //TODO #419 20160601: ->so it should be removed as soon #419 is fixed + //logger.isLoggable does not work because the plugin logger is always ALL + logger.log(Level.FINE, message + ' ' + Thread.currentThread().getName(), new Exception()); + + if (useLogging) { + writeLog("Debug: " + Thread.currentThread().getName() + ':' + message); + } + } } /** @@ -71,9 +103,11 @@ public final class ConsoleLogger { dateTime = DATE_FORMAT.format(new Date()); } try { - Files.write(logFile.toPath(), (dateTime + ": " + message + NEW_LINE).getBytes(), - StandardOpenOption.APPEND, - StandardOpenOption.CREATE); + fileWriter.write(dateTime); + fileWriter.write(": "); + fileWriter.write(message); + fileWriter.write(NEW_LINE); + fileWriter.flush(); } catch (IOException ignored) { } } @@ -93,10 +127,21 @@ public final class ConsoleLogger { * Logs a Throwable with the provided message and saves the stack trace to the log file. * * @param message The message to accompany the exception - * @param th The Throwable to log + * @param th The Throwable to log */ public static void logException(String message, Throwable th) { showError(message + " " + StringUtils.formatException(th)); writeStackTrace(th); } + + public static void close() { + if (fileWriter != null) { + try { + fileWriter.flush(); + fileWriter.close(); + fileWriter = null; + } catch (IOException ignored) { + } + } + } } diff --git a/src/main/java/fr/xephi/authme/DataManager.java b/src/main/java/fr/xephi/authme/DataManager.java index 061683fa..feaa69d4 100644 --- a/src/main/java/fr/xephi/authme/DataManager.java +++ b/src/main/java/fr/xephi/authme/DataManager.java @@ -2,52 +2,44 @@ package fr.xephi.authme; import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.PurgeSettings; import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.Utils; -import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.Server; +import javax.inject.Inject; import java.io.File; -import java.util.ArrayList; -import java.util.List; + +import static fr.xephi.authme.util.StringUtils.makePath; +import java.util.Set; /** */ public class DataManager { - private final AuthMe plugin; - private final PluginHooks pluginHooks; - private final BukkitService bukkitService; + @Inject + private Server server; + @Inject + private PluginHooks pluginHooks; + @Inject + private BukkitService bukkitService; + @Inject + private NewSetting settings; + @Inject + private PermissionsManager permissionsManager; - /* - * Constructor. - */ - public DataManager(AuthMe plugin, PluginHooks pluginHooks, BukkitService bukkitService) { - this.plugin = plugin; - this.pluginHooks = pluginHooks; - this.bukkitService = bukkitService; - } + DataManager() { } - private List getOfflinePlayers(List names) { - List result = new ArrayList<>(); - for (OfflinePlayer op : Bukkit.getOfflinePlayers()) { - for (String name : names) { - if (name.equalsIgnoreCase(op.getName())) { - result.add(op); - } - } - } - return result; - } - - public void purgeAntiXray(List cleared) { + public void purgeAntiXray(Set cleared) { int i = 0; File dataFolder = new File("." + File.separator + "plugins" + File.separator + "AntiXRayData" + File.separator + "PlayerData"); if (!dataFolder.exists() || !dataFolder.isDirectory()) { return; } + for (String file : dataFolder.list()) { if (cleared.contains(file.toLowerCase())) { File playerFile = new File(dataFolder, file); @@ -56,10 +48,11 @@ public class DataManager { } } } + ConsoleLogger.info("AutoPurge: Removed " + i + " AntiXRayData Files"); } - public synchronized void purgeLimitedCreative(List cleared) { + public synchronized void purgeLimitedCreative(Set cleared) { int i = 0; File dataFolder = new File("." + File.separator + "plugins" + File.separator + "LimitedCreative" + File.separator + "inventories"); @@ -96,18 +89,18 @@ public class DataManager { ConsoleLogger.info("AutoPurge: Removed " + i + " LimitedCreative Survival, Creative and Adventure files"); } - public synchronized void purgeDat(List cleared) { + public synchronized void purgeDat(Set cleared) { int i = 0; - File dataFolder = new File(plugin.getServer().getWorldContainer() - + File.separator + plugin.getSettings().getProperty(PurgeSettings.DEFAULT_WORLD) - + File.separator + "players"); - List offlinePlayers = getOfflinePlayers(cleared); - for (OfflinePlayer player : offlinePlayers) { - File playerFile = new File(dataFolder, Utils.getUUIDorName(player) + ".dat"); + File dataFolder = new File(server.getWorldContainer() + , makePath(settings.getProperty(PurgeSettings.DEFAULT_WORLD), "players")); + + for (OfflinePlayer offlinePlayer : cleared) { + File playerFile = new File(dataFolder, Utils.getUUIDorName(offlinePlayer) + ".dat"); if (playerFile.delete()) { i++; } } + ConsoleLogger.info("AutoPurge: Removed " + i + " .dat Files"); } @@ -116,7 +109,7 @@ public class DataManager { * * @param cleared List of String */ - public void purgeEssentials(List cleared) { + public void purgeEssentials(Set cleared) { int i = 0; File essentialsDataFolder = pluginHooks.getEssentialsDataFolder(); if (essentialsDataFolder == null) { @@ -128,9 +121,9 @@ public class DataManager { if (!userDataFolder.exists() || !userDataFolder.isDirectory()) { return; } - List offlinePlayers = getOfflinePlayers(cleared); - for (OfflinePlayer player : offlinePlayers) { - File playerFile = new File(userDataFolder, Utils.getUUIDorName(player) + ".yml"); + + for (OfflinePlayer offlinePlayer : cleared) { + File playerFile = new File(userDataFolder, Utils.getUUIDorName(offlinePlayer) + ".yml"); if (playerFile.exists() && playerFile.delete()) { i++; } @@ -141,16 +134,12 @@ public class DataManager { // TODO: What is this method for? Is it correct? // TODO: Make it work with OfflinePlayers group data. - public synchronized void purgePermissions(List cleared) { - // Get the permissions manager, and make sure it's valid - PermissionsManager permsMan = plugin.getPermissionsManager(); - if (permsMan == null) { - ConsoleLogger.showError("Unable to access permissions manager instance!"); - return; - } - for (String name : cleared) { - permsMan.removeAllGroups(bukkitService.getPlayerExact(name)); + public synchronized void purgePermissions(Set cleared) { + for (OfflinePlayer offlinePlayer : cleared) { + String name = offlinePlayer.getName(); + permissionsManager.removeAllGroups(bukkitService.getPlayerExact(name)); } + ConsoleLogger.info("AutoPurge: Removed permissions from " + cleared.size() + " player(s)."); } } diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 58960f9d..ca75cee1 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -3,6 +3,8 @@ package fr.xephi.authme.api; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.process.Management; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.util.Utils; @@ -12,6 +14,8 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; +import javax.inject.Inject; + /** * Deprecated API of AuthMe. Please use {@link NewAPI} instead. */ @@ -20,7 +24,9 @@ public class API { public static final String newline = System.getProperty("line.separator"); public static AuthMe instance; + private static DataSource dataSource; private static PasswordSecurity passwordSecurity; + private static Management management; /** * Constructor for the deprecated API. @@ -28,9 +34,12 @@ public class API { * @param instance AuthMe */ @Deprecated - public API(AuthMe instance) { + @Inject + API(AuthMe instance, DataSource dataSource, PasswordSecurity passwordSecurity, Management management) { API.instance = instance; - passwordSecurity = instance.getPasswordSecurity(); + API.dataSource = dataSource; + API.passwordSecurity = passwordSecurity; + API.management = management; } /** @@ -109,7 +118,7 @@ public class API { @Deprecated public static boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); - return instance.getDataSource().isAuthAvailable(player); + return dataSource.isAuthAvailable(player); } /** @@ -144,7 +153,7 @@ public class API { .lastLogin(0) .realName(playerName) .build(); - return instance.getDataSource().saveAuth(auth); + return dataSource.saveAuth(auth); } /** @@ -154,7 +163,7 @@ public class API { */ @Deprecated public static void forceLogin(Player player) { - instance.getManagement().performLogin(player, "dontneed", true); + management.performLogin(player, "dontneed", true); } @Deprecated @@ -170,7 +179,7 @@ public class API { */ @Deprecated public boolean isNPC(Player player) { - return Utils.isNPC(player); + return instance.getPluginHooks().isNpc(player); } } diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 0f543762..5e0063e9 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -1,16 +1,16 @@ package fr.xephi.authme.api; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import javax.inject.Inject; /** * The current API of AuthMe. Recommended method of retrieving the API object: @@ -28,19 +28,11 @@ public class NewAPI { * * @param plugin The AuthMe plugin instance */ + @Inject public NewAPI(AuthMe plugin) { this.plugin = plugin; } - /** - * Constructor for NewAPI. - * - * @param server The server instance - */ - public NewAPI(Server server) { - this.plugin = (AuthMe) server.getPluginManager().getPlugin("AuthMe"); - } - /** * Get the API object for AuthMe. * @@ -96,7 +88,7 @@ public class NewAPI { * @return true if the player is an npc */ public boolean isNPC(Player player) { - return Utils.isNPC(player); + return plugin.getPluginHooks().isNpc(player); } /** @@ -148,10 +140,11 @@ public class NewAPI { } /** - * Register a player with the given password. + * Register an OFFLINE/ONLINE player with the given password. * * @param playerName The player to register * @param password The password to register the player with + * * @return true if the player was registered successfully */ public boolean registerPlayer(String playerName, String password) { @@ -187,13 +180,24 @@ public class NewAPI { } /** - * Register a player with the given password. + * Force an ONLINE player to register. + * + * @param player The player to register + * @param password The password to use + * @param autoLogin Should the player be authenticated automatically after the registration? + */ + public void forceRegister(Player player, String password, boolean autoLogin) { + plugin.getManagement().performRegister(player, password, null, autoLogin); + } + + /** + * Register an ONLINE player with the given password. * * @param player The player to register * @param password The password to use */ public void forceRegister(Player player, String password) { - plugin.getManagement().performRegister(player, password, null); + forceRegister(player, password, true); } /** diff --git a/src/main/java/fr/xephi/authme/cache/CaptchaManager.java b/src/main/java/fr/xephi/authme/cache/CaptchaManager.java index 342ad7c3..ec7deca9 100644 --- a/src/main/java/fr/xephi/authme/cache/CaptchaManager.java +++ b/src/main/java/fr/xephi/authme/cache/CaptchaManager.java @@ -1,81 +1,132 @@ package fr.xephi.authme.cache; +import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.SecuritySettings; +import javax.inject.Inject; import java.util.concurrent.ConcurrentHashMap; /** * Manager for the handling of captchas. */ -public class CaptchaManager { +public class CaptchaManager implements SettingsDependent { - private final int threshold; - private final int captchaLength; private final ConcurrentHashMap playerCounts; private final ConcurrentHashMap captchaCodes; - public CaptchaManager(NewSetting settings) { + private boolean isEnabled; + private int threshold; + private int captchaLength; + + @Inject + CaptchaManager(NewSetting settings) { this.playerCounts = new ConcurrentHashMap<>(); this.captchaCodes = new ConcurrentHashMap<>(); - this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA); - this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH); + loadSettings(settings); } - public void increaseCount(String player) { - String playerLower = player.toLowerCase(); - Integer currentCount = playerCounts.get(playerLower); - if (currentCount == null) { - playerCounts.put(playerLower, 1); - } else { - playerCounts.put(playerLower, currentCount + 1); + /** + * Increases the failure count for the given player. + * + * @param name the player's name + */ + public void increaseCount(String name) { + if (isEnabled) { + String playerLower = name.toLowerCase(); + Integer currentCount = playerCounts.get(playerLower); + if (currentCount == null) { + playerCounts.put(playerLower, 1); + } else { + playerCounts.put(playerLower, currentCount + 1); + } } } /** - * Return whether the given player is required to solve a captcha. + * Returns whether the given player is required to solve a captcha. * - * @param player The player to verify - * @return True if the player has to solve a captcha, false otherwise + * @param name the name of the player to verify + * @return true if the player has to solve a captcha, false otherwise */ - public boolean isCaptchaRequired(String player) { - Integer count = playerCounts.get(player.toLowerCase()); - return count != null && count >= threshold; + public boolean isCaptchaRequired(String name) { + if (isEnabled) { + Integer count = playerCounts.get(name.toLowerCase()); + return count != null && count >= threshold; + } + return false; } /** - * Return the captcha code for the player. Creates one if none present, so call only after - * checking with {@link #isCaptchaRequired}. + * Returns the stored captcha code for the player. * - * @param player The player - * @return The code required for the player + * @param name the player's name + * @return the code the player is required to enter, or null if none registered */ - public String getCaptchaCode(String player) { - String code = captchaCodes.get(player.toLowerCase()); - if (code == null) { - code = RandomString.generate(captchaLength); - captchaCodes.put(player.toLowerCase(), code); - } + public String getCaptchaCode(String name) { + return captchaCodes.get(name.toLowerCase()); + } + + /** + * Returns the stored captcha for the player or generates and saves a new one. + * + * @param name the player's name + * @return the code the player is required to enter + */ + public String getCaptchaCodeOrGenerateNew(String name) { + String code = getCaptchaCode(name); + return code == null ? generateCode(name) : code; + } + + /** + * Generates a code for the player and returns it. + * + * @param name the name of the player to generate a code for + * @return the generated code + */ + public String generateCode(String name) { + String code = RandomString.generate(captchaLength); + captchaCodes.put(name.toLowerCase(), code); return code; } /** - * Return whether the supplied code is correct for the given player. + * Checks the given code against the existing one and resets the player's auth failure count upon success. * - * @param player The player to check - * @param code The supplied code - * @return True if the code matches or if no captcha is required for the player, false otherwise + * @param name the name of the player to check + * @param code the supplied code + * @return true if the code matches or if no captcha is required for the player, false otherwise */ - public boolean checkCode(String player, String code) { - String savedCode = captchaCodes.get(player.toLowerCase()); + public boolean checkCode(String name, String code) { + String savedCode = captchaCodes.get(name.toLowerCase()); if (savedCode == null) { return true; } else if (savedCode.equalsIgnoreCase(code)) { - captchaCodes.remove(player.toLowerCase()); + captchaCodes.remove(name.toLowerCase()); + playerCounts.remove(name.toLowerCase()); return true; } return false; } + /** + * Resets the login count of the given player to 0. + * + * @param name the player's name + */ + public void resetCounts(String name) { + if (isEnabled) { + captchaCodes.remove(name.toLowerCase()); + playerCounts.remove(name.toLowerCase()); + } + } + + @Override + public void loadSettings(NewSetting settings) { + this.isEnabled = settings.getProperty(SecuritySettings.USE_CAPTCHA); + this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA); + this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH); + } + } diff --git a/src/main/java/fr/xephi/authme/cache/TempbanManager.java b/src/main/java/fr/xephi/authme/cache/TempbanManager.java new file mode 100644 index 00000000..0a55353c --- /dev/null +++ b/src/main/java/fr/xephi/authme/cache/TempbanManager.java @@ -0,0 +1,116 @@ +package fr.xephi.authme.cache; + +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.Utils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manager for handling tempbans + */ +// TODO Gnat008 20160613: Figure out the best way to remove entries based on time +public class TempbanManager implements SettingsDependent { + + private static final long MINUTE_IN_MILLISECONDS = 60000; + + private final ConcurrentHashMap ipLoginFailureCounts; + private final BukkitService bukkitService; + private final Messages messages; + + private boolean isEnabled; + private int threshold; + private int length; + + @Inject + TempbanManager(BukkitService bukkitService, Messages messages, NewSetting settings) { + this.ipLoginFailureCounts = new ConcurrentHashMap<>(); + this.bukkitService = bukkitService; + this.messages = messages; + loadSettings(settings); + } + + /** + * Increases the failure count for the given IP address. + * + * @param address The player's IP address + */ + public void increaseCount(String address) { + if (isEnabled) { + Integer count = ipLoginFailureCounts.get(address); + + if (count == null) { + ipLoginFailureCounts.put(address, 1); + } else { + ipLoginFailureCounts.put(address, count + 1); + } + } + } + + /** + * Set the failure count for a given IP address to 0. + * + * @param address The IP address + */ + public void resetCount(String address) { + if (isEnabled) { + ipLoginFailureCounts.remove(address); + } + } + + /** + * Return whether the IP address should be tempbanned. + * + * @param address The player's IP address + * @return True if the IP should be tempbanned + */ + public boolean shouldTempban(String address) { + if (isEnabled) { + Integer count = ipLoginFailureCounts.get(address); + return count != null && count >= threshold; + } + + return false; + } + + /** + * Tempban a player's IP address for failing to log in too many times. + * This calculates the expire time based on the time the method was called. + * + * @param player The player to tempban + */ + public void tempbanPlayer(final Player player) { + if (isEnabled) { + final String ip = Utils.getPlayerIp(player); + final String reason = messages.retrieveSingle(MessageKey.TEMPBAN_MAX_LOGINS); + + final Date expires = new Date(); + long newTime = expires.getTime() + (length * MINUTE_IN_MILLISECONDS); + expires.setTime(newTime); + + bukkitService.scheduleSyncDelayedTask(new Runnable() { + @Override + public void run() { + bukkitService.banIp(ip, reason, expires, "AuthMe"); + player.kickPlayer(reason); + } + }); + + resetCount(ip); + } + } + + @Override + public void loadSettings(NewSetting settings) { + this.isEnabled = settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS); + this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TEMPBAN); + this.length = settings.getProperty(SecuritySettings.TEMPBAN_LENGTH); + } +} diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java index 17cdca55..9914ee3f 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerCache.java @@ -1,5 +1,7 @@ package fr.xephi.authme.cache.auth; +import fr.xephi.authme.ConsoleLogger; + import java.util.concurrent.ConcurrentHashMap; /** @@ -22,6 +24,7 @@ public class PlayerCache { if (singleton == null) { singleton = new PlayerCache(); } + return singleton; } @@ -31,6 +34,7 @@ public class PlayerCache { * @param auth PlayerAuth */ public void addPlayer(PlayerAuth auth) { + ConsoleLogger.debug("ADDED PLAYER TO CACHE " + auth.getNickname()); cache.put(auth.getNickname().toLowerCase(), auth); } @@ -40,6 +44,7 @@ public class PlayerCache { * @param auth PlayerAuth */ public void updatePlayer(PlayerAuth auth) { + ConsoleLogger.debug("UPDATE PLAYER " + auth.getNickname()); cache.put(auth.getNickname(), auth); } @@ -49,6 +54,7 @@ public class PlayerCache { * @param user String */ public void removePlayer(String user) { + ConsoleLogger.debug("REMOVE PLAYER " + user); cache.remove(user.toLowerCase()); } diff --git a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java index 49eaf9ee..97670a88 100644 --- a/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/cache/limbo/LimboCache.java @@ -1,46 +1,34 @@ package fr.xephi.authme.cache.limbo; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.backup.PlayerData; 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.concurrent.ConcurrentHashMap; import static com.google.common.base.Preconditions.checkNotNull; /** + * Manages all {@link LimboPlayer} instances. */ public class LimboCache { - private volatile static LimboCache singleton; - private final ConcurrentHashMap cache; - private final AuthMe plugin; - private final JsonCache jsonCache; + private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private final JsonCache jsonCache = new JsonCache(); - /** - * Constructor for LimboCache. - * - * @param plugin AuthMe - */ - private LimboCache(AuthMe plugin) { - this.plugin = plugin; - this.cache = new ConcurrentHashMap<>(); - this.jsonCache = new JsonCache(); - } + @Inject + private PermissionsManager permissionsManager; + @Inject + private SpawnLoader spawnLoader; - /** - * Method getInstance. - * - * @return LimboCache - */ - public static LimboCache getInstance() { - if (singleton == null) { - singleton = new LimboCache(AuthMe.getInstance()); - } - return singleton; + @Inject + LimboCache(PermissionsManager permissionsManager, SpawnLoader spawnLoader) { + this.permissionsManager = permissionsManager; + this.spawnLoader = spawnLoader; } /** @@ -50,13 +38,12 @@ public class LimboCache { */ public void addLimboPlayer(Player player) { String name = player.getName().toLowerCase(); - Location loc = player.getLocation(); + Location location = player.isDead() ? spawnLoader.getSpawnLocation(player) : player.getLocation(); boolean operator = player.isOp(); boolean flyEnabled = player.getAllowFlight(); String playerGroup = ""; - PermissionsManager permsMan = plugin.getPermissionsManager(); - if (permsMan.hasGroupSupport()) { - playerGroup = permsMan.getPrimaryGroup(player); + if (permissionsManager.hasGroupSupport()) { + playerGroup = permissionsManager.getPrimaryGroup(player); } if (jsonCache.doesCacheExist(player)) { @@ -68,11 +55,8 @@ public class LimboCache { } } - if (player.isDead()) { - loc = plugin.getSpawnLocation(player); - } - cache.put(name, new LimboPlayer(name, loc, operator, playerGroup, flyEnabled)); + cache.put(name, new LimboPlayer(name, location, operator, playerGroup, flyEnabled)); } /** @@ -83,9 +67,9 @@ public class LimboCache { public void deleteLimboPlayer(String name) { checkNotNull(name); name = name.toLowerCase(); - if (cache.containsKey(name)) { - cache.get(name).clearTasks(); - cache.remove(name); + LimboPlayer cachedPlayer = cache.remove(name); + if (cachedPlayer != null) { + cachedPlayer.clearTasks(); } } diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 22a3f05c..36ad6da6 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command; -import fr.xephi.authme.permission.DefaultPermission; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.StringUtils; @@ -14,7 +13,7 @@ import static java.util.Arrays.asList; /** * Command description – defines which labels ("names") will lead to a command and points to the * {@link ExecutableCommand} implementation that executes the logic of the command. - * + *

* CommandDescription instances are built hierarchically: they have one parent, or {@code null} for base commands * (main commands such as {@code /authme}), and may have multiple children extending the mapping of the parent: e.g. if * {@code /authme} has a child whose label is {@code "register"}, then {@code /authme register} is the command that @@ -36,9 +35,9 @@ public class CommandDescription { */ private String detailedDescription; /** - * The executable command instance described by this object. + * The class implementing the command described by this object. */ - private ExecutableCommand executableCommand; + private Class executableCommand; /** * The parent command. */ @@ -52,13 +51,13 @@ public class CommandDescription { */ private List arguments; /** - * Command permissions required to execute this command. + * Permission node required to execute this command. */ - private CommandPermissions permissions; + private PermissionNode permission; /** * Private constructor. Use {@link CommandDescription#builder()} to create instances of this class. - *

+ *

* Note for developers: Instances should be created with {@link CommandDescription#createInstance} to be properly * registered in the command tree. */ @@ -68,21 +67,20 @@ public class CommandDescription { /** * Create an instance. * - * @param labels List of command labels. - * @param description Command description. - * @param detailedDescription Detailed comment description. - * @param executableCommand The executable command, or null. - * @param parent Parent command. - * @param arguments Command arguments. - * @param permissions The permissions required to execute this command. + * @param labels command labels + * @param description description of the command + * @param detailedDescription detailed command description + * @param executableCommand class of the command implementation + * @param parent parent command + * @param arguments command arguments + * @param permission permission node required to execute this command * - * @return The created instance + * @return the created instance * @see CommandDescription#builder() */ private static CommandDescription createInstance(List labels, String description, - String detailedDescription, ExecutableCommand executableCommand, - CommandDescription parent, List arguments, - CommandPermissions permissions) { + String detailedDescription, Class executableCommand, CommandDescription parent, + List arguments, PermissionNode permission) { CommandDescription instance = new CommandDescription(); instance.labels = labels; instance.description = description; @@ -90,7 +88,7 @@ public class CommandDescription { instance.executableCommand = executableCommand; instance.parent = parent; instance.arguments = arguments; - instance.permissions = permissions; + instance.permission = permission; if (parent != null) { parent.addChild(instance); @@ -114,7 +112,7 @@ public class CommandDescription { } /** - * Check whether this command description has a specific command. + * Check whether this command description has the given label. * * @param commandLabel The label to check for. * @@ -130,18 +128,18 @@ public class CommandDescription { } /** - * Return the {@link ExecutableCommand} instance defined by the command description. + * Return the {@link ExecutableCommand} class implementing this command. * - * @return The executable command object. + * @return The executable command class */ - public ExecutableCommand getExecutableCommand() { + public Class getExecutableCommand() { return executableCommand; } /** * Return the parent. * - * @return The parent command, or null for base commands. + * @return The parent command, or null for base commands */ public CommandDescription getParent() { return parent; @@ -196,12 +194,12 @@ public class CommandDescription { } /** - * Return the permissions required to execute the command. + * Return the permission node required to execute the command. * - * @return The command permissions, or null if none are required to execute the command. + * @return The permission node, or null if none are required to execute the command. */ - public CommandPermissions getCommandPermissions() { - return permissions; + public PermissionNode getPermission() { + return permission; } /** @@ -220,10 +218,10 @@ public class CommandDescription { private List labels; private String description; private String detailedDescription; - private ExecutableCommand executableCommand; + private Class executableCommand; private CommandDescription parent; private List arguments = new ArrayList<>(); - private CommandPermissions permissions; + private PermissionNode permission; /** * Build a CommandDescription from the builder or throw an exception if a mandatory @@ -239,7 +237,7 @@ public class CommandDescription { // parents and permissions may be null; arguments may be empty return createInstance(labels, description, detailedDescription, executableCommand, - parent, arguments, permissions); + parent, arguments, permission); } public CommandBuilder labels(List labels) { @@ -261,7 +259,7 @@ public class CommandDescription { return this; } - public CommandBuilder executableCommand(ExecutableCommand executableCommand) { + public CommandBuilder executableCommand(Class executableCommand) { this.executableCommand = executableCommand; return this; } @@ -286,9 +284,14 @@ public class CommandDescription { return this; } - public CommandBuilder permissions(DefaultPermission defaultPermission, - PermissionNode... permissionNodes) { - this.permissions = new CommandPermissions(asList(permissionNodes), defaultPermission); + /** + * Add a permission node that a user must have to execute the command. + * + * @param permission The PermissionNode to add + * @return The builder + */ + public CommandBuilder permission(PermissionNode permission) { + this.permission = permission; return this; } } diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java index 37c1a2a1..da5299e2 100644 --- a/src/main/java/fr/xephi/authme/command/CommandHandler.java +++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java @@ -1,18 +1,23 @@ package fr.xephi.authme.command; -import java.util.ArrayList; -import java.util.List; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; -import fr.xephi.authme.util.StringUtils; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** - * The AuthMe command handler, responsible for mapping incoming commands to the correct {@link CommandDescription} - * or to display help messages for unknown invocations. + * The AuthMe command handler, responsible for invoking the correct {@link ExecutableCommand} based on incoming + * command labels or for displaying a help message for unknown command labels. */ public class CommandHandler { @@ -22,15 +27,22 @@ public class CommandHandler { */ private static final double SUGGEST_COMMAND_THRESHOLD = 0.75; - private final CommandService commandService; + private final CommandMapper commandMapper; + private final PermissionsManager permissionsManager; + private final HelpProvider helpProvider; /** - * Create a command handler. - * - * @param commandService The CommandService instance + * Map with ExecutableCommand children. The key is the type of the value. */ - public CommandHandler(CommandService commandService) { - this.commandService = commandService; + private Map, ExecutableCommand> commands = new HashMap<>(); + + @Inject + public CommandHandler(AuthMeServiceInitializer initializer, CommandMapper commandMapper, + PermissionsManager permissionsManager, HelpProvider helpProvider) { + this.commandMapper = commandMapper; + this.permissionsManager = permissionsManager; + this.helpProvider = helpProvider; + initializeCommands(initializer, commandMapper.getCommandClasses()); } /** @@ -48,7 +60,7 @@ public class CommandHandler { List parts = skipEmptyArguments(bukkitArgs); parts.add(0, bukkitCommandLabel); - FoundCommandResult result = commandService.mapPartsToCommand(sender, parts); + FoundCommandResult result = commandMapper.mapPartsToCommand(sender, parts); handleCommandResult(sender, result); return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus()); } @@ -75,6 +87,18 @@ public class CommandHandler { } } + /** + * Initialize all required ExecutableCommand objects. + * + * @param commandClasses the classes to instantiate + */ + private void initializeCommands(AuthMeServiceInitializer initializer, + Set> commandClasses) { + for (Class clazz : commandClasses) { + commands.put(clazz, initializer.newInstance(clazz)); + } + } + /** * Execute the command for the given command sender. * @@ -82,9 +106,9 @@ public class CommandHandler { * @param result The mapped result */ private void executeCommand(CommandSender sender, FoundCommandResult result) { - ExecutableCommand executableCommand = result.getCommandDescription().getExecutableCommand(); + ExecutableCommand executableCommand = commands.get(result.getCommandDescription().getExecutableCommand()); List arguments = result.getArguments(); - executableCommand.executeCommand(sender, arguments, commandService); + executableCommand.executeCommand(sender, arguments); } /** @@ -125,14 +149,14 @@ public class CommandHandler { private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) { CommandDescription command = result.getCommandDescription(); - if (!commandService.getPermissionsManager().hasPermission(sender, command)) { + if (!permissionsManager.hasPermission(sender, command.getPermission())) { sendPermissionDeniedError(sender); return; } // Show the command argument help sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!"); - commandService.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS); + helpProvider.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS); List labels = result.getLabels(); String childLabel = labels.size() >= 2 ? labels.get(1) : ""; diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 6635545f..ce02b9a8 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -41,25 +41,33 @@ import java.util.Collection; import java.util.List; import java.util.Set; -import static fr.xephi.authme.permission.DefaultPermission.ALLOWED; -import static fr.xephi.authme.permission.DefaultPermission.OP_ONLY; - /** * Initializes all available AuthMe commands. */ -public final class CommandInitializer { +public class CommandInitializer { - private CommandInitializer() { - // Helper class + private Set commands; + + public CommandInitializer() { + buildCommands(); } - public static Set buildCommands() { + /** + * Returns the description of all AuthMe commands. + * + * @return the command descriptions + */ + public Set getCommands() { + return commands; + } + + private void buildCommands() { // Register the base AuthMe Reloaded command final CommandDescription AUTHME_BASE = CommandDescription.builder() .labels("authme") .description("Main command") .detailedDescription("The main AuthMeReloaded command. The root for all admin commands.") - .executableCommand(new AuthMeCommand()) + .executableCommand(AuthMeCommand.class) .build(); // Register the register command @@ -70,8 +78,8 @@ public final class CommandInitializer { .detailedDescription("Register the specified player with the specified password.") .withArgument("player", "Player name", false) .withArgument("password", "Password", false) - .permissions(OP_ONLY, AdminPermission.REGISTER) - .executableCommand(new RegisterAdminCommand()) + .permission(AdminPermission.REGISTER) + .executableCommand(RegisterAdminCommand.class) .build(); // Register the unregister command @@ -81,8 +89,8 @@ public final class CommandInitializer { .description("Unregister a player") .detailedDescription("Unregister the specified player.") .withArgument("player", "Player name", false) - .permissions(OP_ONLY, AdminPermission.UNREGISTER) - .executableCommand(new UnregisterAdminCommand()) + .permission(AdminPermission.UNREGISTER) + .executableCommand(UnregisterAdminCommand.class) .build(); // Register the forcelogin command @@ -92,8 +100,8 @@ public final class CommandInitializer { .description("Enforce login player") .detailedDescription("Enforce the specified player to login.") .withArgument("player", "Online player name", true) - .permissions(OP_ONLY, AdminPermission.FORCE_LOGIN) - .executableCommand(new ForceLoginCommand()) + .permission(AdminPermission.FORCE_LOGIN) + .executableCommand(ForceLoginCommand.class) .build(); // Register the changepassword command @@ -104,8 +112,8 @@ public final class CommandInitializer { .detailedDescription("Change the password of a player.") .withArgument("player", "Player name", false) .withArgument("pwd", "New password", false) - .permissions(OP_ONLY, AdminPermission.CHANGE_PASSWORD) - .executableCommand(new ChangePasswordAdminCommand()) + .permission(AdminPermission.CHANGE_PASSWORD) + .executableCommand(ChangePasswordAdminCommand.class) .build(); // Register the last login command @@ -115,8 +123,8 @@ public final class CommandInitializer { .description("Player's last login") .detailedDescription("View the date of the specified players last login.") .withArgument("player", "Player name", true) - .permissions(OP_ONLY, AdminPermission.LAST_LOGIN) - .executableCommand(new LastLoginCommand()) + .permission(AdminPermission.LAST_LOGIN) + .executableCommand(LastLoginCommand.class) .build(); // Register the accounts command @@ -126,8 +134,8 @@ public final class CommandInitializer { .description("Display player accounts") .detailedDescription("Display all accounts of a player by his player name or IP.") .withArgument("player", "Player name or IP", true) - .permissions(OP_ONLY, AdminPermission.ACCOUNTS) - .executableCommand(new AccountsCommand()) + .permission(AdminPermission.ACCOUNTS) + .executableCommand(AccountsCommand.class) .build(); // Register the getemail command @@ -137,8 +145,8 @@ public final class CommandInitializer { .description("Display player's email") .detailedDescription("Display the email address of the specified player if set.") .withArgument("player", "Player name", true) - .permissions(OP_ONLY, AdminPermission.GET_EMAIL) - .executableCommand(new GetEmailCommand()) + .permission(AdminPermission.GET_EMAIL) + .executableCommand(GetEmailCommand.class) .build(); // Register the setemail command @@ -149,8 +157,8 @@ public final class CommandInitializer { .detailedDescription("Change the email address of the specified player.") .withArgument("player", "Player name", false) .withArgument("email", "Player email", false) - .permissions(OP_ONLY, AdminPermission.CHANGE_EMAIL) - .executableCommand(new SetEmailCommand()) + .permission(AdminPermission.CHANGE_EMAIL) + .executableCommand(SetEmailCommand.class) .build(); // Register the getip command @@ -160,8 +168,8 @@ public final class CommandInitializer { .description("Get player's IP") .detailedDescription("Get the IP address of the specified online player.") .withArgument("player", "Player name", false) - .permissions(OP_ONLY, AdminPermission.GET_IP) - .executableCommand(new GetIpCommand()) + .permission(AdminPermission.GET_IP) + .executableCommand(GetIpCommand.class) .build(); // Register the spawn command @@ -170,8 +178,8 @@ public final class CommandInitializer { .labels("spawn", "home") .description("Teleport to spawn") .detailedDescription("Teleport to the spawn.") - .permissions(OP_ONLY, AdminPermission.SPAWN) - .executableCommand(new SpawnCommand()) + .permission(AdminPermission.SPAWN) + .executableCommand(SpawnCommand.class) .build(); // Register the setspawn command @@ -180,8 +188,8 @@ public final class CommandInitializer { .labels("setspawn", "chgspawn") .description("Change the spawn") .detailedDescription("Change the player's spawn to your current position.") - .permissions(OP_ONLY, AdminPermission.SET_SPAWN) - .executableCommand(new SetSpawnCommand()) + .permission(AdminPermission.SET_SPAWN) + .executableCommand(SetSpawnCommand.class) .build(); // Register the firstspawn command @@ -190,8 +198,8 @@ public final class CommandInitializer { .labels("firstspawn", "firsthome") .description("Teleport to first spawn") .detailedDescription("Teleport to the first spawn.") - .permissions(OP_ONLY, AdminPermission.FIRST_SPAWN) - .executableCommand(new FirstSpawnCommand()) + .permission(AdminPermission.FIRST_SPAWN) + .executableCommand(FirstSpawnCommand.class) .build(); // Register the setfirstspawn command @@ -200,8 +208,8 @@ public final class CommandInitializer { .labels("setfirstspawn", "chgfirstspawn") .description("Change the first spawn") .detailedDescription("Change the first player's spawn to your current position.") - .permissions(OP_ONLY, AdminPermission.SET_FIRST_SPAWN) - .executableCommand(new SetFirstSpawnCommand()) + .permission(AdminPermission.SET_FIRST_SPAWN) + .executableCommand(SetFirstSpawnCommand.class) .build(); // Register the purge command @@ -211,8 +219,8 @@ public final class CommandInitializer { .description("Purge old data") .detailedDescription("Purge old AuthMeReloaded data longer than the specified amount of days ago.") .withArgument("days", "Number of days", false) - .permissions(OP_ONLY, AdminPermission.PURGE) - .executableCommand(new PurgeCommand()) + .permission(AdminPermission.PURGE) + .executableCommand(PurgeCommand.class) .build(); // Register the purgelastposition command @@ -223,8 +231,8 @@ public final class CommandInitializer { .description("Purge player's last position") .detailedDescription("Purge the last know position of the specified player or all of them.") .withArgument("player/*", "Player name or * for all players", false) - .permissions(OP_ONLY, AdminPermission.PURGE_LAST_POSITION) - .executableCommand(new PurgeLastPositionCommand()) + .permission(AdminPermission.PURGE_LAST_POSITION) + .executableCommand(PurgeLastPositionCommand.class) .build(); // Register the purgebannedplayers command @@ -233,8 +241,8 @@ public final class CommandInitializer { .labels("purgebannedplayers", "purgebannedplayer", "deletebannedplayers", "deletebannedplayer") .description("Purge banned players data") .detailedDescription("Purge all AuthMeReloaded data for banned players.") - .permissions(OP_ONLY, AdminPermission.PURGE_BANNED_PLAYERS) - .executableCommand(new PurgeBannedPlayersCommand()) + .permission(AdminPermission.PURGE_BANNED_PLAYERS) + .executableCommand(PurgeBannedPlayersCommand.class) .build(); // Register the switchantibot command @@ -244,8 +252,8 @@ public final class CommandInitializer { .description("Switch AntiBot mode") .detailedDescription("Switch or toggle the AntiBot mode to the specified state.") .withArgument("mode", "ON / OFF", true) - .permissions(OP_ONLY, AdminPermission.SWITCH_ANTIBOT) - .executableCommand(new SwitchAntiBotCommand()) + .permission(AdminPermission.SWITCH_ANTIBOT) + .executableCommand(SwitchAntiBotCommand.class) .build(); // Register the reload command @@ -254,8 +262,8 @@ public final class CommandInitializer { .labels("reload", "rld") .description("Reload plugin") .detailedDescription("Reload the AuthMeReloaded plugin.") - .permissions(OP_ONLY, AdminPermission.RELOAD) - .executableCommand(new ReloadCommand()) + .permission(AdminPermission.RELOAD) + .executableCommand(ReloadCommand.class) .build(); // Register the version command @@ -265,7 +273,7 @@ public final class CommandInitializer { .description("Version info") .detailedDescription("Show detailed information about the installed AuthMeReloaded version, the " + "developers, contributors, and license.") - .executableCommand(new VersionCommand()) + .executableCommand(VersionCommand.class) .build(); CommandDescription.builder() @@ -275,19 +283,19 @@ public final class CommandInitializer { .detailedDescription("Converter command for AuthMeReloaded.") .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / " + "royalauth / vauth / sqlitetosql", false) - .permissions(OP_ONLY, AdminPermission.CONVERTER) - .executableCommand(new ConverterCommand()) + .permission(AdminPermission.CONVERTER) + .executableCommand(ConverterCommand.class) .build(); // Register the base login command final CommandDescription LOGIN_BASE = CommandDescription.builder() .parent(null) - .labels("login", "l") + .labels("login", "l", "log") .description("Login command") .detailedDescription("Command to log in using AuthMeReloaded.") .withArgument("password", "Login password", false) - .permissions(ALLOWED, PlayerPermission.LOGIN) - .executableCommand(new LoginCommand()) + .permission(PlayerPermission.LOGIN) + .executableCommand(LoginCommand.class) .build(); // Register the base logout command @@ -296,8 +304,8 @@ public final class CommandInitializer { .labels("logout") .description("Logout command") .detailedDescription("Command to logout using AuthMeReloaded.") - .permissions(ALLOWED, PlayerPermission.LOGOUT) - .executableCommand(new LogoutCommand()) + .permission(PlayerPermission.LOGOUT) + .executableCommand(LogoutCommand.class) .build(); // Register the base register command @@ -308,19 +316,19 @@ public final class CommandInitializer { .detailedDescription("Command to register using AuthMeReloaded.") .withArgument("password", "Password", true) .withArgument("verifyPassword", "Verify password", true) - .permissions(ALLOWED, PlayerPermission.REGISTER) - .executableCommand(new RegisterCommand()) + .permission(PlayerPermission.REGISTER) + .executableCommand(RegisterCommand.class) .build(); // Register the base unregister command CommandDescription UNREGISTER_BASE = CommandDescription.builder() .parent(null) - .labels("unreg", "unregister") + .labels("unregister", "unreg") .description("Unregistration Command") .detailedDescription("Command to unregister using AuthMeReloaded.") .withArgument("password", "Password", false) - .permissions(ALLOWED, PlayerPermission.UNREGISTER) - .executableCommand(new UnregisterCommand()) + .permission(PlayerPermission.UNREGISTER) + .executableCommand(UnregisterCommand.class) .build(); // Register the base changepassword command @@ -331,17 +339,17 @@ public final class CommandInitializer { .detailedDescription("Command to change your password using AuthMeReloaded.") .withArgument("oldPassword", "Old Password", false) .withArgument("newPassword", "New Password.", false) - .permissions(ALLOWED, PlayerPermission.CHANGE_PASSWORD) - .executableCommand(new ChangePasswordCommand()) + .permission(PlayerPermission.CHANGE_PASSWORD) + .executableCommand(ChangePasswordCommand.class) .build(); // Register the base Email command CommandDescription EMAIL_BASE = CommandDescription.builder() .parent(null) - .labels("email", "mail") + .labels("email") .description("Email command") .detailedDescription("The AuthMeReloaded Email command base.") - .executableCommand(new EmailBaseCommand()) + .executableCommand(EmailBaseCommand.class) .build(); // Register the add command @@ -352,8 +360,8 @@ public final class CommandInitializer { .detailedDescription("Add a new email address to your account.") .withArgument("email", "Email address", false) .withArgument("verifyEmail", "Email address verification", false) - .permissions(ALLOWED, PlayerPermission.ADD_EMAIL) - .executableCommand(new AddEmailCommand()) + .permission(PlayerPermission.ADD_EMAIL) + .executableCommand(AddEmailCommand.class) .build(); // Register the change command @@ -364,8 +372,8 @@ public final class CommandInitializer { .detailedDescription("Change an email address of your account.") .withArgument("oldEmail", "Old email address", false) .withArgument("newEmail", "New email address", false) - .permissions(ALLOWED, PlayerPermission.CHANGE_EMAIL) - .executableCommand(new ChangeEmailCommand()) + .permission(PlayerPermission.CHANGE_EMAIL) + .executableCommand(ChangeEmailCommand.class) .build(); // Register the recover command @@ -376,19 +384,19 @@ public final class CommandInitializer { .detailedDescription("Recover your account using an Email address by sending a mail containing " + "a new password.") .withArgument("email", "Email address", false) - .permissions(ALLOWED, PlayerPermission.RECOVER_EMAIL) - .executableCommand(new RecoverEmailCommand()) + .permission(PlayerPermission.RECOVER_EMAIL) + .executableCommand(RecoverEmailCommand.class) .build(); // Register the base captcha command CommandDescription CAPTCHA_BASE = CommandDescription.builder() .parent(null) - .labels("captcha", "capt") + .labels("captcha") .description("Captcha Command") .detailedDescription("Captcha command for AuthMeReloaded.") .withArgument("captcha", "The Captcha", false) - .permissions(ALLOWED, PlayerPermission.CAPTCHA) - .executableCommand(new CaptchaCommand()) + .permission(PlayerPermission.CAPTCHA) + .executableCommand(CaptchaCommand.class) .build(); Set baseCommands = ImmutableSet.of( @@ -402,16 +410,15 @@ public final class CommandInitializer { CAPTCHA_BASE); setHelpOnAllBases(baseCommands); - return baseCommands; + commands = baseCommands; } /** - * Set the help command on all base commands, e.g. to register /authme help or /register help. + * Sets the help command on all base commands, e.g. to register /authme help or /register help. * - * @param commands The list of base commands to register a help child command on + * @param commands the list of base commands to register a help child command on */ - private static void setHelpOnAllBases(Collection commands) { - final HelpCommand helpCommandExecutable = new HelpCommand(); + private void setHelpOnAllBases(Collection commands) { final List helpCommandLabels = Arrays.asList("help", "hlp", "h", "sos", "?"); for (CommandDescription base : commands) { @@ -421,7 +428,7 @@ public final class CommandInitializer { .description("View help") .detailedDescription("View detailed help for /" + base.getLabels().get(0) + " commands.") .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) + .executableCommand(HelpCommand.class) .build(); } } diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java index e43ee4bb..44d5b583 100644 --- a/src/main/java/fr/xephi/authme/command/CommandMapper.java +++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java @@ -6,7 +6,9 @@ import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.StringUtils; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -15,8 +17,7 @@ import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; /** - * The AuthMe command handler, responsible for mapping incoming - * command parts to the correct {@link CommandDescription}. + * Maps incoming command parts to the correct {@link CommandDescription}. */ public class CommandMapper { @@ -28,8 +29,9 @@ public class CommandMapper { private final Set baseCommands; private final PermissionsManager permissionsManager; - public CommandMapper(Set baseCommands, PermissionsManager permissionsManager) { - this.baseCommands = baseCommands; + @Inject + public CommandMapper(CommandInitializer commandInitializer, PermissionsManager permissionsManager) { + this.baseCommands = commandInitializer.getCommands(); this.permissionsManager = permissionsManager; } @@ -67,6 +69,23 @@ public class CommandMapper { return getCommandWithSmallestDifference(base, parts); } + /** + * Return all {@link ExecutableCommand} classes referenced in {@link CommandDescription} objects. + * + * @return all classes + * @see CommandInitializer#getCommands + */ + public Set> getCommandClasses() { + Set> classes = new HashSet<>(50); + for (CommandDescription command : baseCommands) { + classes.add(command.getExecutableCommand()); + for (CommandDescription child : command.getChildren()) { + classes.add(child.getExecutableCommand()); + } + } + return classes; + } + private FoundCommandResult getCommandWithSmallestDifference(CommandDescription base, List parts) { // Return the base command with incorrect arg count error if we only have one part if (parts.size() <= 1) { @@ -139,7 +158,7 @@ public class CommandMapper { private static FoundCommandResult transformResultForHelp(FoundCommandResult result) { if (result.getCommandDescription() != null - && HELP_COMMAND_CLASS.isAssignableFrom(result.getCommandDescription().getExecutableCommand().getClass())) { + && HELP_COMMAND_CLASS == result.getCommandDescription().getExecutableCommand()) { // For "/authme help register" we have labels = [authme, help] and arguments = [register] // But for the help command we want labels = [authme, help] and arguments = [authme, register], // so we can use the arguments as the labels to the command to show help for @@ -152,7 +171,7 @@ public class CommandMapper { } private FoundResultStatus getPermissionAwareStatus(CommandSender sender, CommandDescription command) { - if (sender != null && !permissionsManager.hasPermission(sender, command)) { + if (sender != null && !permissionsManager.hasPermission(sender, command.getPermission())) { return FoundResultStatus.NO_PERMISSION; } return FoundResultStatus.SUCCESS; diff --git a/src/main/java/fr/xephi/authme/command/CommandPermissions.java b/src/main/java/fr/xephi/authme/command/CommandPermissions.java deleted file mode 100644 index 0082b858..00000000 --- a/src/main/java/fr/xephi/authme/command/CommandPermissions.java +++ /dev/null @@ -1,52 +0,0 @@ -package fr.xephi.authme.command; - -import fr.xephi.authme.permission.DefaultPermission; -import fr.xephi.authme.permission.PermissionNode; - -import java.util.List; - -/** - */ -public class CommandPermissions { - - /** - * Defines the permission nodes required to have permission to execute this command. - */ - private List permissionNodes; - /** - * Defines the default permission if the permission nodes couldn't be used. - */ - private DefaultPermission defaultPermission; - - /** - * Constructor. - * - * @param permissionNodes The permission nodes required to execute a command. - * @param defaultPermission The default permission if the permission nodes couldn't be used. - */ - public CommandPermissions(List permissionNodes, DefaultPermission defaultPermission) { - this.permissionNodes = permissionNodes; - this.defaultPermission = defaultPermission; - } - - /** - * Get the permission nodes required to execute this command. - * - * @return The permission nodes required to execute this command. - */ - public List getPermissionNodes() { - return this.permissionNodes; - } - - - /** - * Get the default permission if the permission nodes couldn't be used. - * - * @return The default permission. - */ - public DefaultPermission getDefaultPermission() { - return this.defaultPermission; - } - - -} diff --git a/src/main/java/fr/xephi/authme/command/CommandService.java b/src/main/java/fr/xephi/authme/command/CommandService.java index 5c5abfcc..5efe79e8 100644 --- a/src/main/java/fr/xephi/authme/command/CommandService.java +++ b/src/main/java/fr/xephi/authme/command/CommandService.java @@ -1,26 +1,13 @@ package fr.xephi.authme.command; -import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.command.help.HelpProvider; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.process.Management; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.domain.Property; -import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.ValidationService; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import java.util.Collection; -import java.util.List; +import javax.inject.Inject; /** * Service for implementations of {@link ExecutableCommand} to execute some common tasks. @@ -28,39 +15,12 @@ import java.util.List; */ public class CommandService { - private final AuthMe authMe; - private final Messages messages; - private final HelpProvider helpProvider; - private final CommandMapper commandMapper; - private final PasswordSecurity passwordSecurity; - private final PermissionsManager permissionsManager; - private final NewSetting settings; - private final PluginHooks pluginHooks; - private final SpawnLoader spawnLoader; - private final AntiBot antiBot; - private final ValidationService validationService; - private final BukkitService bukkitService; - - /* - * Constructor. - */ - public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages, - PasswordSecurity passwordSecurity, PermissionsManager permissionsManager, NewSetting settings, - PluginHooks pluginHooks, SpawnLoader spawnLoader, AntiBot antiBot, - ValidationService validationService, BukkitService bukkitService) { - this.authMe = authMe; - this.messages = messages; - this.helpProvider = helpProvider; - this.commandMapper = commandMapper; - this.passwordSecurity = passwordSecurity; - this.permissionsManager = permissionsManager; - this.settings = settings; - this.pluginHooks = pluginHooks; - this.spawnLoader = spawnLoader; - this.antiBot = antiBot; - this.validationService = validationService; - this.bukkitService = bukkitService; - } + @Inject + private Messages messages; + @Inject + private NewSetting settings; + @Inject + private ValidationService validationService; /** * Send a message to a player. @@ -83,86 +43,6 @@ public class CommandService { messages.send(sender, messageKey, replacements); } - /** - * Map command parts to a command description. - * - * @param sender The command sender issuing the request (for permission check), or null to skip permissions - * @param commandParts The received command parts to map to a command - * @return The computed mapping result - */ - public FoundCommandResult mapPartsToCommand(CommandSender sender, List commandParts) { - return commandMapper.mapPartsToCommand(sender, commandParts); - } - - /** - * Run the given task asynchronously with the Bukkit scheduler. - * - * @param task The task to run - */ - public void runTaskAsynchronously(Runnable task) { - authMe.getServer().getScheduler().runTaskAsynchronously(authMe, task); - } - - /** - * Return the AuthMe data source. - * - * @return The used data source - */ - public DataSource getDataSource() { - return authMe.getDataSource(); - } - - /** - * Return the AuthMe instance for further manipulation. Use only if other methods from - * the command service cannot be used. - * - * @return The AuthMe instance - */ - public AuthMe getAuthMe() { - return authMe; - } - - /** - * Return the PasswordSecurity instance. - * - * @return The password security instance - */ - public PasswordSecurity getPasswordSecurity() { - return passwordSecurity; - } - - /** - * Output the help for a given command. - * - * @param sender The sender to output the help to - * @param result The result to output information about - * @param options Output options, see {@link HelpProvider} - */ - public void outputHelp(CommandSender sender, FoundCommandResult result, int options) { - List lines = helpProvider.printHelp(sender, result, options); - for (String line : lines) { - sender.sendMessage(line); - } - } - - /** - * Return the management instance of the plugin. - * - * @return The Management instance linked to the AuthMe instance - */ - public Management getManagement() { - return authMe.getManagement(); - } - - /** - * Return the permissions manager. - * - * @return the permissions manager - */ - public PermissionsManager getPermissionsManager() { - return permissionsManager; - } - /** * Retrieve a message by its message key. * @@ -193,33 +73,6 @@ public class CommandService { return settings; } - public PlayerCache getPlayerCache() { - return PlayerCache.getInstance(); - } - - public PluginHooks getPluginHooks() { - return pluginHooks; - } - - public SpawnLoader getSpawnLoader() { - return spawnLoader; - } - - public AntiBot getAntiBot() { - return antiBot; - } - - /** - * Verifies whether a password is valid according to the plugin settings. - * - * @param password the password to verify - * @param username the username the password is associated with - * @return message key with the password error, or {@code null} if password is valid - */ - public MessageKey validatePassword(String password, String username) { - return validationService.validatePassword(password, username); - } - public boolean validateEmail(String email) { return validationService.validateEmail(email); } @@ -228,16 +81,4 @@ public class CommandService { return validationService.isEmailFreeForRegistration(email, sender); } - public Player getPlayer(String name) { - return bukkitService.getPlayerExact(name); - } - - public Collection getOnlinePlayers() { - return bukkitService.getOnlinePlayers(); - } - - public BukkitService getBukkitService() { - return bukkitService; - } - } diff --git a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java index 9567089f..ceda12ef 100644 --- a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java +++ b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java @@ -10,12 +10,11 @@ import java.util.List; public interface ExecutableCommand { /** - * Execute the command with the given arguments. + * Executes the command with the given arguments. * - * @param sender The command sender. - * @param arguments The arguments. - * @param commandService The command service. + * @param sender the command sender (initiator of the command) + * @param arguments the arguments */ - void executeCommand(CommandSender sender, List arguments, CommandService commandService); + void executeCommand(CommandSender sender, List arguments); } diff --git a/src/main/java/fr/xephi/authme/command/PlayerCommand.java b/src/main/java/fr/xephi/authme/command/PlayerCommand.java index 2d7aca0b..8ce6e05f 100644 --- a/src/main/java/fr/xephi/authme/command/PlayerCommand.java +++ b/src/main/java/fr/xephi/authme/command/PlayerCommand.java @@ -1,19 +1,19 @@ package fr.xephi.authme.command; -import java.util.List; - import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.List; + /** * Common base type for player-only commands, handling the verification that the command sender is indeed a player. */ public abstract class PlayerCommand implements ExecutableCommand { @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { if (sender instanceof Player) { - runCommand((Player) sender, arguments, commandService); + runCommand((Player) sender, arguments); } else { String alternative = getAlternativeCommand(); if (alternative != null) { @@ -25,19 +25,18 @@ public abstract class PlayerCommand implements ExecutableCommand { } /** - * Run the command with the given player and arguments. + * Runs the command with the given player and arguments. * - * @param player The player who initiated the command - * @param arguments The arguments supplied with the command - * @param commandService The command service + * @param player the player who initiated the command + * @param arguments the arguments supplied with the command */ - protected abstract void runCommand(Player player, List arguments, CommandService commandService); + protected abstract void runCommand(Player player, List arguments); /** - * Return an alternative command (textual representation) that is not restricted to players only. - * Example: {@code "authme register "} + * Returns an alternative command (textual representation) that is not restricted to players only. + * Example: {@code "/authme register "} * - * @return Alternative command not only for players, or null if not applicable + * @return Alternative command not restricted to players, or null if not applicable */ protected String getAlternativeCommand() { return null; diff --git a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java index 89256535..2076bcbc 100644 --- a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java @@ -1,6 +1,6 @@ package fr.xephi.authme.command.executable; -import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.command.CommandMapper; import fr.xephi.authme.command.CommandUtils; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.FoundCommandResult; @@ -9,6 +9,7 @@ import fr.xephi.authme.command.help.HelpProvider; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; @@ -16,11 +17,18 @@ import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; public class HelpCommand implements ExecutableCommand { + @Inject + private CommandMapper commandMapper; + + @Inject + private HelpProvider helpProvider; + + // Convention: arguments is not the actual invoked arguments but the command that was invoked, // e.g. "/authme help register" would typically be arguments = [register], but here we pass [authme, register] @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { - FoundCommandResult result = commandService.mapPartsToCommand(sender, arguments); + public void executeCommand(CommandSender sender, List arguments) { + FoundCommandResult result = commandMapper.mapPartsToCommand(sender, arguments); FoundResultStatus resultStatus = result.getResultStatus(); if (MISSING_BASE_COMMAND.equals(resultStatus)) { @@ -38,9 +46,9 @@ public class HelpCommand implements ExecutableCommand { int mappedCommandLevel = result.getCommandDescription().getLabelCount(); if (mappedCommandLevel == 1) { - commandService.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN); + helpProvider.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN); } else { - commandService.outputHelp(sender, result, HelpProvider.ALL_OPTIONS); + helpProvider.outputHelp(sender, result, HelpProvider.ALL_OPTIONS); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java index d3698cfd..3b446a7f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java @@ -3,10 +3,13 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.StringUtils; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; /** @@ -14,25 +17,27 @@ import java.util.List; */ public class AccountsCommand implements ExecutableCommand { + @Inject + private DataSource dataSource; + + @Inject + private BukkitService bukkitService; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(final CommandSender sender, List arguments, - final CommandService commandService) { + public void executeCommand(final CommandSender sender, List arguments) { final String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0); // Assumption: a player name cannot contain '.' - if (!playerName.contains(".")) { - commandService.runTaskAsynchronously(new Runnable() { + if (playerName.contains(".")) { + bukkitService.runTaskAsynchronously(new Runnable() { @Override public void run() { - PlayerAuth auth = commandService.getDataSource().getAuth(playerName.toLowerCase()); - if (auth == null) { - commandService.send(sender, MessageKey.UNKNOWN_USER); - return; - } - - List accountList = commandService.getDataSource().getAllAuthsByIp(auth.getIp()); + List accountList = dataSource.getAllAuthsByIp(playerName); if (accountList.isEmpty()) { - commandService.send(sender, MessageKey.USER_NOT_REGISTERED); + sender.sendMessage("[AuthMe] This IP does not exist in the database."); } else if (accountList.size() == 1) { sender.sendMessage("[AuthMe] " + playerName + " is a single account player"); } else { @@ -41,12 +46,18 @@ public class AccountsCommand implements ExecutableCommand { } }); } else { - commandService.runTaskAsynchronously(new Runnable() { + bukkitService.runTaskAsynchronously(new Runnable() { @Override public void run() { - List accountList = commandService.getDataSource().getAllAuthsByIp(playerName); + PlayerAuth auth = dataSource.getAuth(playerName.toLowerCase()); + if (auth == null) { + commandService.send(sender, MessageKey.UNKNOWN_USER); + return; + } + + List accountList = dataSource.getAllAuthsByIp(auth.getIp()); if (accountList.isEmpty()) { - sender.sendMessage("[AuthMe] This IP does not exist in the database."); + commandService.send(sender, MessageKey.USER_NOT_REGISTERED); } else if (accountList.size() == 1) { sender.sendMessage("[AuthMe] " + playerName + " is a single account player"); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java index 2c0ef601..2648177e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AuthMeCommand.java @@ -1,7 +1,6 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -14,7 +13,7 @@ import java.util.List; public class AuthMeCommand implements ExecutableCommand { @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { sender.sendMessage(ChatColor.GREEN + "This server is running " + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + " b" + AuthMe.getPluginBuildNumber()+ "! " + ChatColor.RED + "<3"); sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/authme help" + ChatColor.YELLOW diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java index 867443ed..729a4b4e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java @@ -2,13 +2,19 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; /** @@ -16,30 +22,46 @@ import java.util.List; */ public class ChangePasswordAdminCommand implements ExecutableCommand { + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private PlayerCache playerCache; + + @Inject + private DataSource dataSource; + + @Inject + private BukkitService bukkitService; + + @Inject + private ValidationService validationService; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(final CommandSender sender, List arguments, - final CommandService commandService) { + public void executeCommand(final CommandSender sender, List arguments) { // Get the player and password final String playerName = arguments.get(0); final String playerPass = arguments.get(1); // Validate the password - MessageKey passwordError = commandService.validatePassword(playerPass, playerName); - if (passwordError != null) { - commandService.send(sender, passwordError); + ValidationResult validationResult = validationService.validatePassword(playerPass, playerName); + if (validationResult.hasError()) { + commandService.send(sender, validationResult.getMessageKey(), validationResult.getArgs()); return; } // Set the password final String playerNameLowerCase = playerName.toLowerCase(); - commandService.runTaskAsynchronously(new Runnable() { + bukkitService.runTaskAsynchronously(new Runnable() { @Override public void run() { - DataSource dataSource = commandService.getDataSource(); PlayerAuth auth = null; - if (commandService.getPlayerCache().isAuthenticated(playerNameLowerCase)) { - auth = commandService.getPlayerCache().getAuth(playerNameLowerCase); + if (playerCache.isAuthenticated(playerNameLowerCase)) { + auth = playerCache.getAuth(playerNameLowerCase); } else if (dataSource.isAuthAvailable(playerNameLowerCase)) { auth = dataSource.getAuth(playerNameLowerCase); } @@ -48,15 +70,14 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { return; } - HashedPassword hashedPassword = commandService.getPasswordSecurity() - .computeHash(playerPass, playerNameLowerCase); + HashedPassword hashedPassword = passwordSecurity.computeHash(playerPass, playerNameLowerCase); auth.setPassword(hashedPassword); - if (!dataSource.updatePassword(auth)) { - commandService.send(sender, MessageKey.ERROR); - } else { + if (dataSource.updatePassword(auth)) { commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); ConsoleLogger.info(playerNameLowerCase + "'s password changed"); + } else { + commandService.send(sender, MessageKey.ERROR); } } 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 e3414480..e18c9dc4 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 @@ -1,6 +1,6 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; +import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.converter.Converter; @@ -10,18 +10,30 @@ import fr.xephi.authme.converter.RoyalAuthConverter; import fr.xephi.authme.converter.SqliteToSql; import fr.xephi.authme.converter.vAuthConverter; import fr.xephi.authme.converter.xAuthConverter; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; +/** + * Converter command: launches conversion based on its parameters. + */ public class ConverterCommand implements ExecutableCommand { - @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { - // AuthMe plugin instance - final AuthMe plugin = AuthMe.getInstance(); + @Inject + private CommandService commandService; + @Inject + private BukkitService bukkitService; + + @Inject + private AuthMeServiceInitializer initializer; + + @Override + public void executeCommand(final CommandSender sender, List arguments) { // Get the conversion job String job = arguments.get(0); @@ -33,49 +45,35 @@ public class ConverterCommand implements ExecutableCommand { } // Get the proper converter instance - Converter converter = null; - switch (jobType) { - case XAUTH: - converter = new xAuthConverter(plugin, sender); - break; - case CRAZYLOGIN: - converter = new CrazyLoginConverter(plugin, sender); - break; - case RAKAMAK: - converter = new RakamakConverter(plugin, sender); - break; - case ROYALAUTH: - converter = new RoyalAuthConverter(plugin); - break; - case VAUTH: - converter = new vAuthConverter(plugin, sender); - break; - case SQLITETOSQL: - converter = new SqliteToSql(plugin, sender, commandService.getSettings()); - break; - default: - break; - } + final Converter converter = initializer.newInstance(jobType.getConverterClass()); // Run the convert job - commandService.runTaskAsynchronously(converter); + bukkitService.runTaskAsynchronously(new Runnable() { + @Override + public void run() { + converter.execute(sender); + } + }); // Show a status message sender.sendMessage("[AuthMe] Successfully converted from " + jobType.getName()); } - public enum ConvertType { - XAUTH("xauth"), - CRAZYLOGIN("crazylogin"), - RAKAMAK("rakamak"), - ROYALAUTH("royalauth"), - VAUTH("vauth"), - SQLITETOSQL("sqlitetosql"); + @VisibleForTesting + enum ConvertType { + XAUTH("xauth", xAuthConverter.class), + CRAZYLOGIN("crazylogin", CrazyLoginConverter.class), + RAKAMAK("rakamak", RakamakConverter.class), + ROYALAUTH("royalauth", RoyalAuthConverter.class), + VAUTH("vauth", vAuthConverter.class), + SQLITETOSQL("sqlitetosql", SqliteToSql.class); - final String name; + private final String name; + private final Class converterClass; - ConvertType(String name) { + ConvertType(String name, Class converterClass) { this.name = name; + this.converterClass = converterClass; } public static ConvertType fromName(String name) { @@ -87,8 +85,12 @@ public class ConverterCommand implements ExecutableCommand { return null; } - String getName() { + public String getName() { return this.name; } + + public Class getConverterClass() { + return converterClass; + } } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java index 50b3b169..acfc59be 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommand.java @@ -1,9 +1,10 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.settings.SpawnLoader; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; /** @@ -11,12 +12,15 @@ import java.util.List; */ public class FirstSpawnCommand extends PlayerCommand { + @Inject + private SpawnLoader spawnLoader; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { - if (commandService.getSpawnLoader().getFirstSpawn() != null) { - player.teleport(commandService.getSpawnLoader().getFirstSpawn()); - } else { + public void runCommand(Player player, List arguments) { + if (spawnLoader.getFirstSpawn() == null) { player.sendMessage("[AuthMe] First spawn has failed, please try to define the first spawn"); + } else { + player.teleport(spawnLoader.getFirstSpawn()); } } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java index c974e7ea..706e313e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ForceLoginCommand.java @@ -1,10 +1,13 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; import static fr.xephi.authme.permission.PlayerPermission.CAN_LOGIN_BE_FORCED; @@ -14,18 +17,27 @@ import static fr.xephi.authme.permission.PlayerPermission.CAN_LOGIN_BE_FORCED; */ public class ForceLoginCommand implements ExecutableCommand { + @Inject + private PermissionsManager permissionsManager; + + @Inject + private Management management; + + @Inject + private BukkitService bukkitService; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { // Get the player query String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0); - Player player = commandService.getPlayer(playerName); + Player player = bukkitService.getPlayerExact(playerName); if (player == null || !player.isOnline()) { sender.sendMessage("Player needs to be online!"); - } else if (!commandService.getPermissionsManager().hasPermission(player, CAN_LOGIN_BE_FORCED)) { + } else if (!permissionsManager.hasPermission(player, CAN_LOGIN_BE_FORCED)) { sender.sendMessage("You cannot force login the player " + playerName + "!"); } else { - commandService.getManagement().performLogin(player, "dontneed", true); + management.performLogin(player, "dontneed", true); sender.sendMessage("Force login for " + playerName + " performed!"); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java index 53a3051c..07ee57d3 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetEmailCommand.java @@ -3,9 +3,11 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; /** @@ -13,11 +15,17 @@ import java.util.List; */ public class GetEmailCommand implements ExecutableCommand { + @Inject + private DataSource dataSource; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0); - PlayerAuth auth = commandService.getDataSource().getAuth(playerName); + PlayerAuth auth = dataSource.getAuth(playerName); if (auth == null) { commandService.send(sender, MessageKey.UNKNOWN_USER); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java index 59379aa7..773f01b4 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/GetIpCommand.java @@ -1,20 +1,24 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class GetIpCommand implements ExecutableCommand { + @Inject + private BukkitService bukkitService; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { // Get the player query String playerName = arguments.get(0); - Player player = commandService.getPlayer(playerName); + Player player = bukkitService.getPlayerExact(playerName); if (player == null) { sender.sendMessage("The player is not online"); return; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java index 05fdab2b..376499fb 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/LastLoginCommand.java @@ -3,9 +3,11 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.Date; import java.util.List; @@ -14,12 +16,18 @@ import java.util.List; */ public class LastLoginCommand implements ExecutableCommand { + @Inject + private DataSource dataSource; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { // Get the player String playerName = (arguments.size() >= 1) ? arguments.get(0) : sender.getName(); - PlayerAuth auth = commandService.getDataSource().getAuth(playerName); + PlayerAuth auth = dataSource.getAuth(playerName); if (auth == null) { commandService.send(sender, MessageKey.USER_NOT_REGISTERED); return; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java index 0ace9424..c80a790d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java @@ -1,14 +1,18 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.settings.properties.PurgeSettings; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.task.PurgeTask; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -import java.util.ArrayList; +import javax.inject.Inject; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Command for purging data of banned players. Depending on the settings @@ -16,30 +20,30 @@ import java.util.List; */ public class PurgeBannedPlayersCommand implements ExecutableCommand { - @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { - // AuthMe plugin instance - final AuthMe plugin = commandService.getAuthMe(); + @Inject + private DataSource dataSource; + @Inject + private AuthMe plugin; + + @Inject + private BukkitService bukkitService; + + @Override + public void executeCommand(CommandSender sender, List arguments) { // Get the list of banned players - List bannedPlayers = new ArrayList<>(); - for (OfflinePlayer offlinePlayer : plugin.getServer().getBannedPlayers()) { - bannedPlayers.add(offlinePlayer.getName().toLowerCase()); + Set namedBanned = new HashSet<>(); + Set bannedPlayers = bukkitService.getBannedPlayers(); + for (OfflinePlayer offlinePlayer : bannedPlayers) { + namedBanned.add(offlinePlayer.getName().toLowerCase()); } + //todo: note this should may run async because it may executes a SQL-Query // Purge the banned players - commandService.getDataSource().purgeBanned(bannedPlayers); - if (commandService.getProperty(PurgeSettings.REMOVE_ESSENTIALS_FILES) - && commandService.getPluginHooks().isEssentialsAvailable()) - plugin.dataManager.purgeEssentials(bannedPlayers); - if (commandService.getProperty(PurgeSettings.REMOVE_PLAYER_DAT)) - plugin.dataManager.purgeDat(bannedPlayers); - if (commandService.getProperty(PurgeSettings.REMOVE_LIMITED_CREATIVE_INVENTORIES)) - plugin.dataManager.purgeLimitedCreative(bannedPlayers); - if (commandService.getProperty(PurgeSettings.REMOVE_ANTI_XRAY_FILE)) - plugin.dataManager.purgeAntiXray(bannedPlayers); + dataSource.purgeBanned(namedBanned); // Show a status message - sender.sendMessage("[AuthMe] Database has been purged correctly"); + sender.sendMessage(ChatColor.GOLD + "Purging user accounts..."); + new PurgeTask(plugin, sender, namedBanned, bannedPlayers).runTaskTimer(plugin, 0, 1); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeCommand.java index 43e1daa5..73e8e7c0 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeCommand.java @@ -1,14 +1,16 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.settings.properties.PurgeSettings; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.task.PurgeTask; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.Calendar; import java.util.List; +import java.util.Set; /** * Command for purging the data of players which have not been since for a given number @@ -18,8 +20,14 @@ public class PurgeCommand implements ExecutableCommand { private static final int MINIMUM_LAST_SEEN_DAYS = 30; + @Inject + private DataSource dataSource; + + @Inject + private AuthMe plugin; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { // Get the days parameter String daysStr = arguments.get(0); @@ -44,25 +52,13 @@ public class PurgeCommand implements ExecutableCommand { calendar.add(Calendar.DATE, -days); long until = calendar.getTimeInMillis(); + //todo: note this should may run async because it may executes a SQL-Query // Purge the data, get the purged values - List purged = commandService.getDataSource().autoPurgeDatabase(until); + Set purged = dataSource.autoPurgeDatabase(until); // Show a status message sender.sendMessage(ChatColor.GOLD + "Deleted " + purged.size() + " user accounts"); - - // Purge other data - AuthMe plugin = commandService.getAuthMe(); - if (commandService.getProperty(PurgeSettings.REMOVE_ESSENTIALS_FILES) && - commandService.getPluginHooks().isEssentialsAvailable()) - plugin.dataManager.purgeEssentials(purged); - if (commandService.getProperty(PurgeSettings.REMOVE_PLAYER_DAT)) - plugin.dataManager.purgeDat(purged); - if (commandService.getProperty(PurgeSettings.REMOVE_LIMITED_CREATIVE_INVENTORIES)) - plugin.dataManager.purgeLimitedCreative(purged); - if (commandService.getProperty(PurgeSettings.REMOVE_ANTI_XRAY_FILE)) - plugin.dataManager.purgeAntiXray(purged); - - // Show a status message - sender.sendMessage(ChatColor.GREEN + "[AuthMe] Database has been purged correctly"); + sender.sendMessage(ChatColor.GOLD + "Purging user accounts..."); + new PurgeTask(plugin, sender, purged).runTaskTimer(plugin, 0, 1); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommand.java index 118f8460..ca6a0200 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommand.java @@ -3,9 +3,11 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; /** @@ -13,26 +15,32 @@ import java.util.List; */ public class PurgeLastPositionCommand implements ExecutableCommand { + @Inject + private DataSource dataSource; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(final CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(final CommandSender sender, List arguments) { String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0); if ("*".equals(playerName)) { - for (PlayerAuth auth : commandService.getDataSource().getAllAuths()) { + for (PlayerAuth auth : dataSource.getAllAuths()) { resetLastPosition(auth); - commandService.getDataSource().updateQuitLoc(auth); + dataSource.updateQuitLoc(auth); } sender.sendMessage("All players last position locations are now reset"); } else { // Get the user auth and make sure the user exists - PlayerAuth auth = commandService.getDataSource().getAuth(playerName); + PlayerAuth auth = dataSource.getAuth(playerName); if (auth == null) { commandService.send(sender, MessageKey.UNKNOWN_USER); return; } resetLastPosition(auth); - commandService.getDataSource().updateQuitLoc(auth); + dataSource.updateQuitLoc(auth); sender.sendMessage(playerName + "'s last position location is now reset"); } } 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 2c842aee..a4931e96 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,68 +1,85 @@ package fr.xephi.authme.command.executable.authme; -import java.util.List; - -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; /** * Admin command to register a user. */ public class RegisterAdminCommand implements ExecutableCommand { + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private CommandService commandService; + + @Inject + private DataSource dataSource; + + @Inject + private BukkitService bukkitService; + + @Inject + private ValidationService validationService; + @Override - public void executeCommand(final CommandSender sender, List arguments, - final CommandService commandService) { + public void executeCommand(final CommandSender sender, List arguments) { // Get the player name and password final String playerName = arguments.get(0); final String playerPass = arguments.get(1); final String playerNameLowerCase = playerName.toLowerCase(); // Command logic - MessageKey passwordError = commandService.validatePassword(playerPass, playerName); - if (passwordError != null) { - commandService.send(sender, passwordError); + ValidationResult passwordValidation = validationService.validatePassword(playerPass, playerName); + if (passwordValidation.hasError()) { + commandService.send(sender, passwordValidation.getMessageKey(), passwordValidation.getArgs()); return; } - commandService.runTaskAsynchronously(new Runnable() { + bukkitService.runTaskAsynchronously(new Runnable() { @Override public void run() { - if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { + if (dataSource.isAuthAvailable(playerNameLowerCase)) { commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); return; } - HashedPassword hashedPassword = commandService.getPasswordSecurity() - .computeHash(playerPass, playerNameLowerCase); + HashedPassword hashedPassword = passwordSecurity.computeHash(playerPass, playerNameLowerCase); PlayerAuth auth = PlayerAuth.builder() .name(playerNameLowerCase) .realName(playerName) .password(hashedPassword) .build(); - if (!commandService.getDataSource().saveAuth(auth)) { + if (!dataSource.saveAuth(auth)) { commandService.send(sender, MessageKey.ERROR); return; } - commandService.getDataSource().setUnlogged(playerNameLowerCase); + dataSource.setUnlogged(playerNameLowerCase); commandService.send(sender, MessageKey.REGISTER_SUCCESS); ConsoleLogger.info(sender.getName() + " registered " + playerName); - Player player = commandService.getPlayer(playerName); + final Player player = bukkitService.getPlayerExact(playerName); if (player != null) { - final Player p = player; - p.getServer().getScheduler().scheduleSyncDelayedTask(commandService.getAuthMe(), new Runnable() { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { - p.kickPlayer("An admin just registered you, please log in again"); + player.kickPlayer("An admin just registered you, please log in again"); } }); } 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 91df5832..73534902 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 @@ -4,9 +4,14 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.DatabaseSettings; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; /** @@ -14,11 +19,32 @@ import java.util.List; */ public class ReloadCommand implements ExecutableCommand { + @Inject + private AuthMe plugin; + + @Inject + private AuthMeServiceInitializer initializer; + + @Inject + private NewSetting settings; + + @Inject + private DataSource dataSource; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { - AuthMe plugin = commandService.getAuthMe(); + public void executeCommand(CommandSender sender, List arguments) { try { - plugin.reload(); + settings.reload(); + 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"); + } + initializer.performReloadOnServices(); commandService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); } catch (Exception e) { sender.sendMessage("Error occurred during reload of AuthMe: aborting"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SetEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SetEmailCommand.java index 0e7bcf35..76ef4959 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SetEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SetEmailCommand.java @@ -6,8 +6,10 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.List; /** @@ -15,9 +17,20 @@ import java.util.List; */ public class SetEmailCommand implements ExecutableCommand { + @Inject + private DataSource dataSource; + + @Inject + private CommandService commandService; + + @Inject + private PlayerCache playerCache; + + @Inject + private BukkitService bukkitService; + @Override - public void executeCommand(final CommandSender sender, List arguments, - final CommandService commandService) { + public void executeCommand(final CommandSender sender, List arguments) { // Get the player name and email address final String playerName = arguments.get(0); final String playerEmail = arguments.get(1); @@ -28,11 +41,10 @@ public class SetEmailCommand implements ExecutableCommand { return; } - commandService.runTaskAsynchronously(new Runnable() { + bukkitService.runTaskAsynchronously(new Runnable() { @Override public void run() { // Validate the user - DataSource dataSource = commandService.getDataSource(); PlayerAuth auth = dataSource.getAuth(playerName); if (auth == null) { commandService.send(sender, MessageKey.UNKNOWN_USER); @@ -50,8 +62,8 @@ public class SetEmailCommand implements ExecutableCommand { } // Update the player cache - if (PlayerCache.getInstance().getAuth(playerName) != null) { - PlayerCache.getInstance().updatePlayer(auth); + if (playerCache.getAuth(playerName) != null) { + playerCache.updatePlayer(auth); } // Show a status message diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommand.java index a3cc7d45..899a1103 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommand.java @@ -1,16 +1,20 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.settings.SpawnLoader; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class SetFirstSpawnCommand extends PlayerCommand { + @Inject + private SpawnLoader spawnLoader; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { - if (commandService.getSpawnLoader().setFirstSpawn(player.getLocation())) { + public void runCommand(Player player, List arguments) { + if (spawnLoader.setFirstSpawn(player.getLocation())) { player.sendMessage("[AuthMe] Correctly defined new first spawn point"); } else { player.sendMessage("[AuthMe] SetFirstSpawn has failed, please retry"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SetSpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SetSpawnCommand.java index 3201ea4c..fc9a67b9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SetSpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SetSpawnCommand.java @@ -1,16 +1,20 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.settings.SpawnLoader; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class SetSpawnCommand extends PlayerCommand { + @Inject + private SpawnLoader spawnLoader; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { - if (commandService.getSpawnLoader().setSpawn(player.getLocation())) { + public void runCommand(Player player, List arguments) { + if (spawnLoader.setSpawn(player.getLocation())) { player.sendMessage("[AuthMe] Correctly defined new spawn point"); } else { player.sendMessage("[AuthMe] SetSpawn has failed, please retry"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java index 04f8e563..3c011e6d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SpawnCommand.java @@ -1,19 +1,23 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.settings.SpawnLoader; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class SpawnCommand extends PlayerCommand { + @Inject + private SpawnLoader spawnLoader; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { - if (commandService.getSpawnLoader().getSpawn() != null) { - player.teleport(commandService.getSpawnLoader().getSpawn()); - } else { + public void runCommand(Player player, List arguments) { + if (spawnLoader.getSpawn() == null) { player.sendMessage("[AuthMe] Spawn has failed, please try to define the spawn"); + } else { + player.teleport(spawnLoader.getSpawn()); } } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java index 3c36c169..64a7241f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java @@ -1,13 +1,14 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.command.CommandMapper; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.help.HelpProvider; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.Arrays; import java.util.List; @@ -16,9 +17,17 @@ import java.util.List; */ public class SwitchAntiBotCommand implements ExecutableCommand { + @Inject + private AntiBot antiBot; + + @Inject + private CommandMapper commandMapper; + + @Inject + private HelpProvider helpProvider; + @Override - public void executeCommand(final CommandSender sender, List arguments, CommandService commandService) { - AntiBot antiBot = commandService.getAntiBot(); + public void executeCommand(final CommandSender sender, List arguments) { if (arguments.isEmpty()) { sender.sendMessage("[AuthMe] AntiBot status: " + antiBot.getAntiBotStatus().name()); return; @@ -35,8 +44,8 @@ public class SwitchAntiBotCommand implements ExecutableCommand { sender.sendMessage("[AuthMe] AntiBot Manual Override: disabled!"); } else { sender.sendMessage(ChatColor.DARK_RED + "Invalid AntiBot mode!"); - FoundCommandResult result = commandService.mapPartsToCommand(sender, Arrays.asList("authme", "antibot")); - commandService.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS); + FoundCommandResult result = commandMapper.mapPartsToCommand(sender, Arrays.asList("authme", "antibot")); + helpProvider.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS); sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/authme help antibot"); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommand.java index 5c8196dd..2e746f0d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommand.java @@ -1,24 +1,25 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.permission.AuthGroupHandler; +import fr.xephi.authme.permission.AuthGroupType; 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 fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitTask; +import javax.inject.Inject; import java.util.List; import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; @@ -28,31 +29,53 @@ import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; */ public class UnregisterAdminCommand implements ExecutableCommand { + @Inject + private DataSource dataSource; + + @Inject + private CommandService commandService; + + @Inject + private PlayerCache playerCache; + + @Inject + private BukkitService bukkitService; + + @Inject + private LimboCache limboCache; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + @Inject + private AuthGroupHandler authGroupHandler; + + @Override - public void executeCommand(final CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(final CommandSender sender, List arguments) { // Get the player name String playerName = arguments.get(0); String playerNameLowerCase = playerName.toLowerCase(); // Make sure the user is valid - if (!commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { + if (!dataSource.isAuthAvailable(playerNameLowerCase)) { commandService.send(sender, MessageKey.UNKNOWN_USER); return; } // Remove the player - if (!commandService.getDataSource().removeAuth(playerNameLowerCase)) { + if (!dataSource.removeAuth(playerNameLowerCase)) { commandService.send(sender, MessageKey.ERROR); return; } // Unregister the player - Player target = commandService.getPlayer(playerNameLowerCase); - PlayerCache.getInstance().removePlayer(playerNameLowerCase); - Utils.setGroup(target, Utils.GroupType.UNREGISTERED); + Player target = bukkitService.getPlayerExact(playerNameLowerCase); + playerCache.removePlayer(playerNameLowerCase); + authGroupHandler.setGroup(target, AuthGroupType.UNREGISTERED); if (target != null && target.isOnline()) { if (commandService.getProperty(RegistrationSettings.FORCE)) { - applyUnregisteredEffectsAndTasks(target, commandService); + applyUnregisteredEffectsAndTasks(target); } commandService.send(target, MessageKey.UNREGISTERED_SUCCESS); } @@ -68,27 +91,18 @@ public class UnregisterAdminCommand implements ExecutableCommand { * timeout kick, blindness. * * @param target the player that was unregistered - * @param service the command service */ - private void applyUnregisteredEffectsAndTasks(Player target, CommandService service) { - final AuthMe plugin = service.getAuthMe(); - final BukkitService bukkitService = service.getBukkitService(); - final String playerNameLowerCase = target.getName().toLowerCase(); - + private void applyUnregisteredEffectsAndTasks(Player target) { + // TODO #765: Remove use of Utils method and behave according to settings Utils.teleportToSpawn(target); - LimboCache.getInstance().addLimboPlayer(target); - int timeOut = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - int interval = service.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - if (timeOut != 0) { - BukkitTask id = bukkitService.runTaskLater(new TimeoutTask(plugin, playerNameLowerCase, target), timeOut); - LimboCache.getInstance().getLimboPlayer(playerNameLowerCase).setTimeoutTask(id); - } - LimboCache.getInstance().getLimboPlayer(playerNameLowerCase).setMessageTask( - bukkitService.runTask(new MessageTask(service.getBukkitService(), plugin.getMessages(), - playerNameLowerCase, MessageKey.REGISTER_MESSAGE, interval))); - if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { - target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + limboCache.addLimboPlayer(target); + limboPlayerTaskManager.registerTimeoutTask(target); + limboPlayerTaskManager.registerMessageTask(target.getName(), false); + + final int timeout = commandService.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; + if (commandService.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { + target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java index 1a15dc04..a7e6575e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java @@ -3,10 +3,12 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.util.BukkitService; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.Collection; import java.util.List; @@ -14,15 +16,21 @@ import static fr.xephi.authme.settings.properties.PluginSettings.HELP_HEADER; public class VersionCommand implements ExecutableCommand { + @Inject + private BukkitService bukkitService; + + @Inject + private CommandService commandService; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + public void executeCommand(CommandSender sender, List arguments) { // Show some version info sender.sendMessage(ChatColor.GOLD + "==========[ " + commandService.getProperty(HELP_HEADER) + " ABOUT ]=========="); sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + ChatColor.GRAY + " (build: " + AuthMe.getPluginBuildNumber() + ")"); sender.sendMessage(ChatColor.GOLD + "Developers:"); - Collection onlinePlayers = commandService.getOnlinePlayers(); + Collection onlinePlayers = bukkitService.getOnlinePlayers(); printDeveloper(sender, "Xephi", "xephi59", "Lead Developer", onlinePlayers); printDeveloper(sender, "DNx5", "DNx5", "Developer", onlinePlayers); printDeveloper(sender, "games647", "games647", "Developer", onlinePlayers); 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 aba0a950..1577c7ad 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 @@ -1,55 +1,47 @@ package fr.xephi.authme.command.executable.captcha; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.cache.CaptchaManager; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.RandomString; -import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class CaptchaCommand extends PlayerCommand { + @Inject + private PlayerCache playerCache; + + @Inject + private CaptchaManager captchaManager; + + @Inject + private CommandService commandService; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { - final String playerNameLowerCase = player.getName().toLowerCase(); - final String captcha = arguments.get(0); - final AuthMe plugin = commandService.getAuthMe(); - PlayerCache playerCache = PlayerCache.getInstance(); + public void runCommand(Player player, List arguments) { + final String playerName = player.getName().toLowerCase(); - // Command logic - if (playerCache.isAuthenticated(playerNameLowerCase)) { + if (playerCache.isAuthenticated(playerName)) { commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); - return; - } - - if (!commandService.getProperty(SecuritySettings.USE_CAPTCHA)) { + } else if (!captchaManager.isCaptchaRequired(playerName)) { commandService.send(player, MessageKey.USAGE_LOGIN); - return; + } else { + checkCaptcha(player, arguments.get(0)); } + } - if (!plugin.cap.containsKey(playerNameLowerCase)) { - commandService.send(player, MessageKey.USAGE_LOGIN); - return; + private void checkCaptcha(Player player, String captchaCode) { + final boolean isCorrectCode = captchaManager.checkCode(player.getName(), captchaCode); + if (isCorrectCode) { + commandService.send(player, MessageKey.CAPTCHA_SUCCESS); + commandService.send(player, MessageKey.LOGIN_MESSAGE); + } else { + String newCode = captchaManager.generateCode(player.getName()); + commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); } - - if (!captcha.equals(plugin.cap.get(playerNameLowerCase))) { - plugin.cap.remove(playerNameLowerCase); - int captchaLength = commandService.getProperty(SecuritySettings.CAPTCHA_LENGTH); - String randStr = RandomString.generate(captchaLength); - plugin.cap.put(playerNameLowerCase, randStr); - commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase)); - return; - } - - plugin.captcha.remove(playerNameLowerCase); - plugin.cap.remove(playerNameLowerCase); - - // Show a status message - commandService.send(player, MessageKey.CAPTCHA_SUCCESS); - commandService.send(player, MessageKey.LOGIN_MESSAGE); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java index 7d378ca8..68377390 100644 --- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java @@ -1,13 +1,15 @@ package fr.xephi.authme.command.executable.changepassword; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.task.ChangePasswordTask; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; /** @@ -15,27 +17,37 @@ import java.util.List; */ public class ChangePasswordCommand extends PlayerCommand { + @Inject + private CommandService commandService; + + @Inject + private PlayerCache playerCache; + + @Inject + private ValidationService validationService; + + @Inject + private Management management; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { String oldPassword = arguments.get(0); String newPassword = arguments.get(1); String name = player.getName().toLowerCase(); - final PlayerCache playerCache = commandService.getPlayerCache(); if (!playerCache.isAuthenticated(name)) { commandService.send(player, MessageKey.NOT_LOGGED_IN); return; } // Make sure the password is allowed - MessageKey passwordError = commandService.validatePassword(newPassword, name); - if (passwordError != null) { - commandService.send(player, passwordError); + ValidationResult passwordValidation = validationService.validatePassword(newPassword, name); + if (passwordValidation.hasError()) { + commandService.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs()); return; } - AuthMe plugin = AuthMe.getInstance(); // TODO ljacqu 20160117: Call async task via Management - commandService.runTaskAsynchronously(new ChangePasswordTask(plugin, player, oldPassword, newPassword)); + management.performPasswordChange(player, oldPassword, newPassword); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java index cd92ebf4..842d866d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java @@ -3,8 +3,10 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.process.Management; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; /** @@ -12,14 +14,20 @@ import java.util.List; */ public class AddEmailCommand extends PlayerCommand { + @Inject + private Management management; + + @Inject + private CommandService commandService; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { String email = arguments.get(0); String emailConfirmation = arguments.get(1); if (email.equals(emailConfirmation)) { // Closer inspection of the mail address handled by the async task - commandService.getManagement().performAddEmail(player, email); + management.performAddEmail(player, email); } else { commandService.send(player, MessageKey.CONFIRM_EMAIL_MESSAGE); } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java index 6d7281ac..1f9051e3 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java @@ -1,9 +1,10 @@ package fr.xephi.authme.command.executable.email; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.process.Management; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; /** @@ -11,11 +12,14 @@ import java.util.List; */ public class ChangeEmailCommand extends PlayerCommand { + @Inject + private Management management; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { String playerMailOld = arguments.get(0); String playerMailNew = arguments.get(1); - commandService.getManagement().performChangeEmail(player, playerMailOld, playerMailNew); + management.performChangeEmail(player, playerMailOld, playerMailNew); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java index 3a79843e..0484e218 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java @@ -1,11 +1,12 @@ package fr.xephi.authme.command.executable.email; -import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.command.CommandMapper; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.help.HelpProvider; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.Collections; import java.util.List; @@ -14,9 +15,15 @@ import java.util.List; */ public class EmailBaseCommand implements ExecutableCommand { + @Inject + private CommandMapper commandMapper; + + @Inject + private HelpProvider helpProvider; + @Override - public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { - FoundCommandResult result = commandService.mapPartsToCommand(sender, Collections.singletonList("email")); - commandService.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN); + public void executeCommand(CommandSender sender, List arguments) { + FoundCommandResult result = commandMapper.mapPartsToCommand(sender, Collections.singletonList("email")); + helpProvider.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index f4a319a5..fac517ea 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -8,28 +8,44 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.util.StringUtils; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class RecoverEmailCommand extends PlayerCommand { + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private CommandService commandService; + + @Inject + private DataSource dataSource; + + @Inject + private PlayerCache playerCache; + + @Inject + // TODO #655: Remove injected AuthMe instance once Authme#mail is encapsulated + private AuthMe plugin; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { final String playerMail = arguments.get(0); final String playerName = player.getName(); - final AuthMe plugin = commandService.getAuthMe(); if (plugin.mail == null) { ConsoleLogger.showError("Mail API is not set"); commandService.send(player, MessageKey.ERROR); return; } - DataSource dataSource = commandService.getDataSource(); if (dataSource.isAuthAvailable(playerName)) { if (PlayerCache.getInstance().isAuthenticated(playerName)) { commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); @@ -37,10 +53,10 @@ public class RecoverEmailCommand extends PlayerCommand { } String thePass = RandomString.generate(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)); - HashedPassword hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName); + HashedPassword hashNew = passwordSecurity.computeHash(thePass, playerName); PlayerAuth auth; - if (PlayerCache.getInstance().isAuthenticated(playerName)) { - auth = PlayerCache.getInstance().getAuth(playerName); + if (playerCache.isAuthenticated(playerName)) { + auth = playerCache.getAuth(playerName); } else if (dataSource.isAuthAvailable(playerName)) { auth = dataSource.getAuth(playerName); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java index 5689ec63..8b4054ac 100644 --- a/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/login/LoginCommand.java @@ -1,9 +1,10 @@ package fr.xephi.authme.command.executable.login; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.process.Management; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; /** @@ -11,9 +12,12 @@ import java.util.List; */ public class LoginCommand extends PlayerCommand { + @Inject + private Management management; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { final String password = arguments.get(0); - commandService.getManagement().performLogin(player, password, false); + management.performLogin(player, password, false); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java index 598d1738..83b17882 100644 --- a/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/logout/LogoutCommand.java @@ -1,9 +1,10 @@ package fr.xephi.authme.command.executable.logout; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.process.Management; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; /** @@ -11,8 +12,11 @@ import java.util.List; */ public class LogoutCommand extends PlayerCommand { + @Inject + private Management management; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { - commandService.getManagement().performLogout(player); + public void runCommand(Player player, List arguments) { + management.performLogout(player); } } 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 d060c56b..ed9f9eef 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 @@ -4,12 +4,14 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; @@ -19,25 +21,31 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.ENABLE_PAS public class RegisterCommand extends PlayerCommand { + @Inject + private Management management; + + @Inject + private CommandService commandService; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { if (commandService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { //for two factor auth we don't need to check the usage - commandService.getManagement().performRegister(player, "", ""); + management.performRegister(player, "", "", true); return; } // Ensure that there is 1 argument, or 2 if confirmation is required - final boolean useConfirmation = isConfirmationRequired(commandService); + final boolean useConfirmation = isConfirmationRequired(); if (arguments.isEmpty() || useConfirmation && arguments.size() < 2) { commandService.send(player, MessageKey.USAGE_REGISTER); return; } if (commandService.getProperty(USE_EMAIL_REGISTRATION)) { - handleEmailRegistration(player, arguments, commandService); + handleEmailRegistration(player, arguments); } else { - handlePasswordRegistration(player, arguments, commandService); + handlePasswordRegistration(player, arguments); } } @@ -46,15 +54,15 @@ public class RegisterCommand extends PlayerCommand { return "/authme register "; } - private void handlePasswordRegistration(Player player, List arguments, CommandService commandService) { + private void handlePasswordRegistration(Player player, List arguments) { if (commandService.getProperty(ENABLE_PASSWORD_CONFIRMATION) && !arguments.get(0).equals(arguments.get(1))) { commandService.send(player, MessageKey.PASSWORD_MATCH_ERROR); } else { - commandService.getManagement().performRegister(player, arguments.get(0), ""); + management.performRegister(player, arguments.get(0), "", true); } } - private void handleEmailRegistration(Player player, List arguments, CommandService commandService) { + private void handleEmailRegistration(Player player, List arguments) { if (commandService.getProperty(EmailSettings.MAIL_ACCOUNT).isEmpty()) { player.sendMessage("Cannot register: no email address is set for the server. " + "Please contact an administrator"); @@ -70,17 +78,16 @@ public class RegisterCommand extends PlayerCommand { commandService.send(player, MessageKey.USAGE_REGISTER); } else { String thePass = RandomString.generate(commandService.getProperty(RECOVERY_PASSWORD_LENGTH)); - commandService.getManagement().performRegister(player, thePass, email); + management.performRegister(player, thePass, email, true); } } /** * Return whether the password or email has to be confirmed. * - * @param commandService The command service * @return True if the confirmation is needed, false otherwise */ - private boolean isConfirmationRequired(CommandService commandService) { + private boolean isConfirmationRequired() { return commandService.getProperty(USE_EMAIL_REGISTRATION) ? commandService.getProperty(ENABLE_CONFIRM_EMAIL) : commandService.getProperty(ENABLE_PASSWORD_CONFIRMATION); diff --git a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java index 195f659e..720b136e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java @@ -4,24 +4,35 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.process.Management; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; public class UnregisterCommand extends PlayerCommand { + @Inject + private Management management; + + @Inject + private CommandService commandService; + + @Inject + private PlayerCache playerCache; + @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { String playerPass = arguments.get(0); final String playerNameLowerCase = player.getName().toLowerCase(); // Make sure the player is authenticated - if (!PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) { + if (!playerCache.isAuthenticated(playerNameLowerCase)) { commandService.send(player, MessageKey.NOT_LOGGED_IN); return; } // Unregister the player - commandService.getManagement().performUnregister(player, playerPass, false); + management.performUnregister(player, playerPass, false); } } diff --git a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java index 7bc06f03..4e07639d 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java @@ -4,16 +4,18 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import fr.xephi.authme.command.CommandArgumentDescription; import fr.xephi.authme.command.CommandDescription; -import fr.xephi.authme.command.CommandPermissions; import fr.xephi.authme.command.CommandUtils; import fr.xephi.authme.command.FoundCommandResult; +import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.permission.DefaultPermission; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.util.CollectionUtils; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.PluginSettings; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.ArrayList; import java.util.List; @@ -23,7 +25,7 @@ import static java.util.Collections.singletonList; /** * Help syntax generator for AuthMe commands. */ -public class HelpProvider { +public class HelpProvider implements SettingsDependent { // --- Bit flags --- /** Set to not show the command. */ @@ -43,14 +45,15 @@ public class HelpProvider { public static final int ALL_OPTIONS = ~HIDE_COMMAND; private final PermissionsManager permissionsManager; - private final String helpHeader; + private String helpHeader; - public HelpProvider(PermissionsManager permissionsManager, String helpHeader) { + @Inject + HelpProvider(PermissionsManager permissionsManager, NewSetting settings) { this.permissionsManager = permissionsManager; - this.helpHeader = helpHeader; + loadSettings(settings); } - public List printHelp(CommandSender sender, FoundCommandResult result, int options) { + private List printHelp(CommandSender sender, FoundCommandResult result, int options) { if (result.getCommandDescription() == null) { return singletonList(ChatColor.DARK_RED + "Failed to retrieve any help information!"); } @@ -84,6 +87,25 @@ public class HelpProvider { return lines; } + /** + * Output the help for a given command. + * + * @param sender The sender to output the help to + * @param result The result to output information about + * @param options Output options, see {@link HelpProvider} + */ + public void outputHelp(CommandSender sender, FoundCommandResult result, int options) { + List lines = printHelp(sender, result, options); + for (String line : lines) { + sender.sendMessage(line); + } + } + + @Override + public void loadSettings(NewSetting settings) { + helpHeader = settings.getProperty(PluginSettings.HELP_HEADER); + } + private static void printDetailedDescription(CommandDescription command, List lines) { lines.add(ChatColor.GOLD + "Short description: " + ChatColor.WHITE + command.getDescription()); lines.add(ChatColor.GOLD + "Detailed description:"); @@ -110,7 +132,6 @@ public class HelpProvider { } private static void printAlternatives(CommandDescription command, List correctLabels, List lines) { - // TODO ljacqu 20151219: Need to show alternatives for base labels too? E.g. /r for /register if (command.getLabels().size() <= 1 || correctLabels.size() <= 1) { return; } @@ -130,25 +151,22 @@ public class HelpProvider { private static void printPermissions(CommandDescription command, CommandSender sender, PermissionsManager permissionsManager, List lines) { - CommandPermissions permissions = command.getCommandPermissions(); - // TODO ljacqu 20151224: Isn't it possible to have a default permission but no permission nodes? - if (permissions == null || CollectionUtils.isEmpty(permissions.getPermissionNodes())) { + PermissionNode permission = command.getPermission(); + if (permission == null) { return; } lines.add(ChatColor.GOLD + "Permissions:"); - for (PermissionNode node : permissions.getPermissionNodes()) { - boolean hasPermission = permissionsManager.hasPermission(sender, node); - final String nodePermsString = "" + ChatColor.GRAY + ChatColor.ITALIC - + (hasPermission ? " (You have permission)" : " (No permission)"); - lines.add(" " + ChatColor.YELLOW + ChatColor.ITALIC + node.getNode() + nodePermsString); - } + boolean hasPermission = permissionsManager.hasPermission(sender, permission); + final String nodePermsString = "" + ChatColor.GRAY + ChatColor.ITALIC + + (hasPermission ? " (You have permission)" : " (No permission)"); + lines.add(" " + ChatColor.YELLOW + ChatColor.ITALIC + permission.getNode() + nodePermsString); // Addendum to the line to specify whether the sender has permission or not when default is OP_ONLY - final DefaultPermission defaultPermission = permissions.getDefaultPermission(); + final DefaultPermission defaultPermission = permission.getDefaultPermission(); String addendum = ""; if (DefaultPermission.OP_ONLY.equals(defaultPermission)) { - addendum = PermissionsManager.evaluateDefaultPermission(defaultPermission, sender) + addendum = defaultPermission.evaluate(sender) ? " (You have permission)" : " (No permission)"; } @@ -156,7 +174,7 @@ public class HelpProvider { + defaultPermission.getTitle() + addendum); // Evaluate if the sender has permission to the command - if (permissionsManager.hasPermission(sender, command)) { + if (permissionsManager.hasPermission(sender, command.getPermission())) { lines.add(ChatColor.GOLD + " Result: " + ChatColor.GREEN + ChatColor.ITALIC + "You have permission"); } else { lines.add(ChatColor.GOLD + " Result: " + ChatColor.DARK_RED + ChatColor.ITALIC + "No permission"); diff --git a/src/main/java/fr/xephi/authme/converter/Converter.java b/src/main/java/fr/xephi/authme/converter/Converter.java index 9068f02f..28f18fd9 100644 --- a/src/main/java/fr/xephi/authme/converter/Converter.java +++ b/src/main/java/fr/xephi/authme/converter/Converter.java @@ -1,7 +1,16 @@ package fr.xephi.authme.converter; +import org.bukkit.command.CommandSender; + /** - * Common supertype for AuthMe converters. + * Interface for AuthMe converters. */ -public interface Converter extends Runnable { +public interface Converter { + + /** + * Execute the conversion. + * + * @param sender the sender who initialized the conversion + */ + void execute(CommandSender sender); } diff --git a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java index c617ccad..5a8c547a 100644 --- a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java +++ b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java @@ -1,47 +1,46 @@ package fr.xephi.authme.converter; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.ConverterSettings; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; /** - * @author Xephi59 + * Converter for CrazyLogin to AuthMe. */ public class CrazyLoginConverter implements Converter { private final DataSource database; - private final CommandSender sender; + private final NewSetting settings; + private final File dataFolder; - /** - * Constructor for CrazyLoginConverter. - * - * @param instance AuthMe - * @param sender CommandSender - */ - public CrazyLoginConverter(AuthMe instance, CommandSender sender) { - this.database = instance.getDataSource(); - this.sender = sender; + @Inject + CrazyLoginConverter(@DataFolder File dataFolder, DataSource dataSource, NewSetting settings) { + this.dataFolder = dataFolder; + this.database = dataSource; + this.settings = settings; } @Override - public void run() { - String fileName = Settings.crazyloginFileName; - try { - File source = new File(AuthMe.getInstance().getDataFolder() + File.separator + fileName); - if (!source.exists()) { - sender.sendMessage("Error while trying to import data, please put " + fileName + " in AuthMe folder!"); - return; - } - String line; - BufferedReader users = new BufferedReader(new FileReader(source)); + public void execute(CommandSender sender) { + String fileName = settings.getProperty(ConverterSettings.CRAZYLOGIN_FILE_NAME); + File source = new File(dataFolder, fileName); + if (!source.exists()) { + sender.sendMessage("CrazyLogin file not found, please put " + fileName + " in AuthMe folder!"); + return; + } + + String line; + try (BufferedReader users = new BufferedReader(new FileReader(source))) { while ((line = users.readLine()) != null) { if (line.contains("|")) { String[] args = line.split("\\|"); @@ -49,22 +48,21 @@ public class CrazyLoginConverter implements Converter { continue; } String playerName = args[0]; - String psw = args[1]; - if (psw != null) { + String password = args[1]; + if (password != null) { PlayerAuth auth = PlayerAuth.builder() .name(playerName.toLowerCase()) .realName(playerName) - .password(psw, null) + .password(password, null) .build(); database.saveAuth(auth); } } } - users.close(); ConsoleLogger.info("CrazyLogin database has been imported correctly"); } catch (IOException ex) { - ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.showError("Can't open the crazylogin database file! Does it exist?"); + ConsoleLogger.logException("Encountered", ex); } } diff --git a/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java b/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java index 7919f6a0..82b2540d 100644 --- a/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java +++ b/src/main/java/fr/xephi/authme/converter/ForceFlatToSqlite.java @@ -12,7 +12,7 @@ import java.util.List; /** * Mandatory migration from the deprecated flat file datasource to SQLite. */ -public class ForceFlatToSqlite implements Converter { +public class ForceFlatToSqlite { private final DataSource source; private final DataSource destination; @@ -31,7 +31,6 @@ public class ForceFlatToSqlite implements Converter { /** * Perform the conversion. */ - @Override public void run() { List skippedPlayers = new ArrayList<>(); for (PlayerAuth auth : source.getAllAuths()) { diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index f1acf300..e568bb8b 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -1,14 +1,16 @@ package fr.xephi.authme.converter; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.ConverterSettings; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -21,24 +23,26 @@ import java.util.Map.Entry; */ public class RakamakConverter implements Converter { - private final AuthMe instance; private final DataSource database; - private final CommandSender sender; + private final NewSetting settings; private final File pluginFolder; + private final PasswordSecurity passwordSecurity; - public RakamakConverter(AuthMe instance, CommandSender sender) { - this.instance = instance; - this.database = instance.getDataSource(); - this.sender = sender; - pluginFolder = instance.getDataFolder(); + @Inject + RakamakConverter(@DataFolder File dataFolder, DataSource dataSource, NewSetting settings, + PasswordSecurity passwordSecurity) { + this.database = dataSource; + this.settings = settings; + this.pluginFolder = dataFolder; + this.passwordSecurity = passwordSecurity; } @Override // TODO ljacqu 20151229: Restructure this into smaller portions - public void run() { - boolean useIP = Settings.rakamakUseIp; - String fileName = Settings.rakamakUsers; - String ipFileName = Settings.rakamakUsersIp; + public void execute(CommandSender sender) { + 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<>(); @@ -60,7 +64,6 @@ public class RakamakConverter implements Converter { ipFile.close(); users = new BufferedReader(new FileReader(source)); - PasswordSecurity passwordSecurity = instance.getPasswordSecurity(); while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); diff --git a/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java b/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java index b79657e2..49cde259 100644 --- a/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java @@ -5,9 +5,11 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import javax.inject.Inject; import java.io.File; import static fr.xephi.authme.util.StringUtils.makePath; @@ -19,13 +21,14 @@ public class RoyalAuthConverter implements Converter { private final AuthMe plugin; private final DataSource dataSource; - public RoyalAuthConverter(AuthMe plugin) { + @Inject + RoyalAuthConverter(AuthMe plugin, DataSource dataSource) { this.plugin = plugin; - this.dataSource = plugin.getDataSource(); + this.dataSource = dataSource; } @Override - public void run() { + public void execute(CommandSender sender) { for (OfflinePlayer player : plugin.getServer().getOfflinePlayers()) { try { String name = player.getName().toLowerCase(); diff --git a/src/main/java/fr/xephi/authme/converter/SqliteToSql.java b/src/main/java/fr/xephi/authme/converter/SqliteToSql.java index 0791613e..61168480 100644 --- a/src/main/java/fr/xephi/authme/converter/SqliteToSql.java +++ b/src/main/java/fr/xephi/authme/converter/SqliteToSql.java @@ -1,40 +1,43 @@ package fr.xephi.authme.converter; -import fr.xephi.authme.settings.NewSetting; -import org.bukkit.command.CommandSender; - -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.settings.NewSetting; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; public class SqliteToSql implements Converter { - private final AuthMe plugin; - private final CommandSender sender; private final NewSetting settings; + private final DataSource dataSource; + private final Messages messages; - public SqliteToSql(AuthMe plugin, CommandSender sender, NewSetting settings) { - this.plugin = plugin; - this.sender = sender; + @Inject + SqliteToSql(NewSetting settings, DataSource dataSource, Messages messages) { this.settings = settings; + this.dataSource = dataSource; + this.messages = messages; } @Override - public void run() { - if (plugin.getDataSource().getType() != DataSourceType.MYSQL) { + public void execute(CommandSender sender) { + if (dataSource.getType() != DataSourceType.MYSQL) { sender.sendMessage("Please configure your mySQL connection and re-run this command"); return; } try { SQLite data = new SQLite(settings); for (PlayerAuth auth : data.getAllAuths()) { - plugin.getDataSource().saveAuth(auth); + dataSource.saveAuth(auth); } } catch (Exception e) { - plugin.getMessages().send(sender, MessageKey.ERROR); + messages.send(sender, MessageKey.ERROR); ConsoleLogger.logException("Problem during SQLite to SQL conversion:", e); } } diff --git a/src/main/java/fr/xephi/authme/converter/vAuthConverter.java b/src/main/java/fr/xephi/authme/converter/vAuthConverter.java index 2e3d9746..7c6f3ea5 100644 --- a/src/main/java/fr/xephi/authme/converter/vAuthConverter.java +++ b/src/main/java/fr/xephi/authme/converter/vAuthConverter.java @@ -4,18 +4,19 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import org.bukkit.command.CommandSender; +import javax.inject.Inject; + public class vAuthConverter implements Converter { private final AuthMe plugin; - private final CommandSender sender; - public vAuthConverter(AuthMe plugin, CommandSender sender) { + @Inject + vAuthConverter(AuthMe plugin) { this.plugin = plugin; - this.sender = sender; } @Override - public void run() { + public void execute(CommandSender sender) { try { new vAuthFileReader(plugin).convert(); } catch (Exception e) { diff --git a/src/main/java/fr/xephi/authme/converter/xAuthConverter.java b/src/main/java/fr/xephi/authme/converter/xAuthConverter.java index 696f86fd..6fdddbf7 100644 --- a/src/main/java/fr/xephi/authme/converter/xAuthConverter.java +++ b/src/main/java/fr/xephi/authme/converter/xAuthConverter.java @@ -3,18 +3,19 @@ package fr.xephi.authme.converter; import fr.xephi.authme.AuthMe; import org.bukkit.command.CommandSender; +import javax.inject.Inject; + public class xAuthConverter implements Converter { private final AuthMe plugin; - private final CommandSender sender; - public xAuthConverter(AuthMe plugin, CommandSender sender) { + @Inject + xAuthConverter(AuthMe plugin) { this.plugin = plugin; - this.sender = sender; } @Override - public void run() { + public void execute(CommandSender sender) { try { Class.forName("de.luricos.bukkit.xAuth.xAuth"); xAuthToFlat converter = new xAuthToFlat(plugin, sender); diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index 6aad92b2..12d45d20 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -8,6 +8,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.ThreadFactoryBuilder; + import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -15,12 +16,11 @@ import fr.xephi.authme.security.crypts.HashedPassword; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -/** - */ public class CacheDataSource implements DataSource { private final DataSource source; @@ -41,7 +41,8 @@ public class CacheDataSource implements DataSource { .build()) ); cachedAuths = CacheBuilder.newBuilder() - .refreshAfterWrite(8, TimeUnit.MINUTES) + .refreshAfterWrite(5, TimeUnit.MINUTES) + .expireAfterAccess(15, TimeUnit.MINUTES) .build(new CacheLoader>() { @Override public Optional load(String key) { @@ -53,6 +54,7 @@ public class CacheDataSource implements DataSource { return executorService.submit(new Callable>() { @Override public Optional call() { + ConsoleLogger.debug("REFRESH " + key); return load(key); } }); @@ -70,7 +72,7 @@ public class CacheDataSource implements DataSource { } @Override - public synchronized boolean isAuthAvailable(String user) { + public boolean isAuthAvailable(String user) { return getAuth(user) != null; } @@ -85,13 +87,13 @@ public class CacheDataSource implements DataSource { } @Override - public synchronized PlayerAuth getAuth(String user) { + public PlayerAuth getAuth(String user) { user = user.toLowerCase(); return cachedAuths.getUnchecked(user).orNull(); } @Override - public synchronized boolean saveAuth(PlayerAuth auth) { + public boolean saveAuth(PlayerAuth auth) { boolean result = source.saveAuth(auth); if (result) { cachedAuths.refresh(auth.getNickname()); @@ -100,7 +102,7 @@ public class CacheDataSource implements DataSource { } @Override - public synchronized boolean updatePassword(PlayerAuth auth) { + public boolean updatePassword(PlayerAuth auth) { boolean result = source.updatePassword(auth); if (result) { cachedAuths.refresh(auth.getNickname()); @@ -137,16 +139,17 @@ public class CacheDataSource implements DataSource { } @Override - public List autoPurgeDatabase(long until) { - List cleared = source.autoPurgeDatabase(until); + public Set autoPurgeDatabase(long until) { + Set cleared = source.autoPurgeDatabase(until); for (String name : cleared) { cachedAuths.invalidate(name); } + return cleared; } @Override - public synchronized boolean removeAuth(String name) { + public boolean removeAuth(String name) { name = name.toLowerCase(); boolean result = source.removeAuth(name); if (result) { @@ -156,7 +159,7 @@ public class CacheDataSource implements DataSource { } @Override - public synchronized void close() { + public void close() { source.close(); cachedAuths.invalidateAll(); executorService.shutdown(); @@ -168,7 +171,7 @@ public class CacheDataSource implements DataSource { } @Override - public synchronized boolean updateEmail(final PlayerAuth auth) { + public boolean updateEmail(final PlayerAuth auth) { boolean result = source.updateEmail(auth); if (result) { cachedAuths.refresh(auth.getNickname()); @@ -177,17 +180,17 @@ public class CacheDataSource implements DataSource { } @Override - public synchronized List getAllAuthsByIp(final String ip) { + public List getAllAuthsByIp(final String ip) { return source.getAllAuthsByIp(ip); } @Override - public synchronized int countAuthsByEmail(final String email) { + public int countAuthsByEmail(final String email) { return source.countAuthsByEmail(email); } @Override - public synchronized void purgeBanned(final List banned) { + public void purgeBanned(final Set banned) { source.purgeBanned(banned); cachedAuths.invalidateAll(banned); } diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index 4c844a83..dd80bc76 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -1,14 +1,16 @@ package fr.xephi.authme.datasource; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.security.crypts.HashedPassword; import java.util.List; +import java.util.Set; /** * Interface for manipulating {@link PlayerAuth} objects from a data source. */ -public interface DataSource { +public interface DataSource extends Reloadable { /** * Return whether there is a record for the given username. @@ -74,7 +76,7 @@ public interface DataSource { * @param until The minimum last login * @return The account names that have been removed */ - List autoPurgeDatabase(long until); + Set autoPurgeDatabase(long until); /** * Remove a user record from the database. @@ -126,7 +128,7 @@ public interface DataSource { * * @param banned the list of players to delete */ - void purgeBanned(List banned); + void purgeBanned(Set banned); /** * Return the data source type. @@ -204,6 +206,7 @@ public interface DataSource { /** * Reload the data source. */ + @Override void reload(); } diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index b36fa279..fa9d291e 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -17,7 +17,9 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Deprecated flat file datasource. The only method guaranteed to work is {@link FlatFile#getAllAuths()} @@ -227,11 +229,11 @@ public class FlatFile implements DataSource { } @Override - public List autoPurgeDatabase(long until) { + public Set autoPurgeDatabase(long until) { BufferedReader br = null; BufferedWriter bw = null; ArrayList lines = new ArrayList<>(); - List cleared = new ArrayList<>(); + Set cleared = new HashSet<>(); try { br = new BufferedReader(new FileReader(source)); String line; @@ -256,6 +258,7 @@ public class FlatFile implements DataSource { silentClose(br); silentClose(bw); } + return cleared; } @@ -414,7 +417,7 @@ public class FlatFile implements DataSource { } @Override - public void purgeBanned(List banned) { + public void purgeBanned(Set banned) { BufferedReader br = null; BufferedWriter bw = null; ArrayList lines = new ArrayList<>(); diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 5182b859..94e43e00 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -3,6 +3,7 @@ package fr.xephi.authme.datasource; import com.google.common.annotations.VisibleForTesting; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; + import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; @@ -24,10 +25,10 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; -/** - */ public class MySQL implements DataSource { private final String host; @@ -105,34 +106,36 @@ public class MySQL implements DataSource { ds = hikariDataSource; } - private synchronized void setConnectionArguments() throws RuntimeException { + private void setConnectionArguments() throws RuntimeException { ds = new HikariDataSource(); ds.setPoolName("AuthMeMYSQLPool"); - ds.setDriverClassName("com.mysql.jdbc.Driver"); - ds.setJdbcUrl("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database); - ds.addDataSourceProperty("rewriteBatchedStatements", "true"); - ds.addDataSourceProperty("jdbcCompliantTruncation", "false"); - ds.addDataSourceProperty("cachePrepStmts", "true"); - ds.addDataSourceProperty("prepStmtCacheSize", "250"); - ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - //set utf-8 as default encoding + // Database URL + ds.setJdbcUrl("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database); + + // Auth + ds.setUsername(this.username); + ds.setPassword(this.password); + + // Encoding ds.addDataSourceProperty("characterEncoding", "utf8"); ds.addDataSourceProperty("encoding","UTF-8"); ds.addDataSourceProperty("useUnicode", "true"); - ds.setUsername(this.username); - ds.setPassword(this.password); - ds.setInitializationFailFast(true); // Don't start the plugin if the database is unavailable - ds.setMaxLifetime(180000); // 3 Min - ds.setIdleTimeout(60000); // 1 Min - ds.setMinimumIdle(2); - ds.setMaximumPoolSize((Runtime.getRuntime().availableProcessors() * 2) + 1); + // Random stuff + ds.addDataSourceProperty("rewriteBatchedStatements", "true"); + ds.addDataSourceProperty("jdbcCompliantTruncation", "false"); + + // Caching + ds.addDataSourceProperty("cachePrepStmts", "true"); + ds.addDataSourceProperty("prepStmtCacheSize", "250"); + ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!"); } @Override - public synchronized void reload() throws RuntimeException { + public void reload() throws RuntimeException { if (ds != null) { ds.close(); } @@ -140,11 +143,11 @@ public class MySQL implements DataSource { ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); } - private synchronized Connection getConnection() throws SQLException { + private Connection getConnection() throws SQLException { return ds.getConnection(); } - private synchronized void setupConnection() throws SQLException { + private void setupConnection() throws SQLException { try (Connection con = getConnection()) { Statement st = con.createStatement(); DatabaseMetaData md = con.getMetaData(); @@ -257,7 +260,7 @@ public class MySQL implements DataSource { } @Override - public synchronized boolean isAuthAvailable(String user) { + public boolean isAuthAvailable(String user) { String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; ResultSet rs = null; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { @@ -293,7 +296,7 @@ public class MySQL implements DataSource { } @Override - public synchronized PlayerAuth getAuth(String user) { + public PlayerAuth getAuth(String user) { String sql = "SELECT * FROM " + tableName + " WHERE " + col.NAME + "=?;"; PlayerAuth auth; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { @@ -327,7 +330,7 @@ public class MySQL implements DataSource { } @Override - public synchronized boolean saveAuth(PlayerAuth auth) { + public boolean saveAuth(PlayerAuth auth) { try (Connection con = getConnection()) { PreparedStatement pst; PreparedStatement pst2; @@ -424,6 +427,7 @@ public class MySQL implements DataSource { rs.close(); pst.close(); } else if (hashAlgorithm == HashAlgorithm.WORDPRESS) { + // NOTE: Eclipse says pst should be closed HERE, but it's a bug, we already close it above. -sgdc3 pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"); pst.setString(1, auth.getNickname()); rs = pst.executeQuery(); @@ -500,6 +504,7 @@ public class MySQL implements DataSource { rs.close(); pst.close(); } else if (hashAlgorithm == HashAlgorithm.XFBCRYPT) { + // NOTE: Eclipse says pst should be closed HERE, but it's a bug, we already close it above. -sgdc3 pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"); pst.setString(1, auth.getNickname()); rs = pst.executeQuery(); @@ -528,7 +533,7 @@ public class MySQL implements DataSource { } @Override - public synchronized boolean updatePassword(PlayerAuth auth) { + public boolean updatePassword(PlayerAuth auth) { return updatePassword(auth.getNickname(), auth.getPassword()); } @@ -591,7 +596,7 @@ public class MySQL implements DataSource { } @Override - public synchronized boolean updateSession(PlayerAuth auth) { + public boolean updateSession(PlayerAuth auth) { String sql = "UPDATE " + tableName + " SET " + col.IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { @@ -608,8 +613,8 @@ public class MySQL implements DataSource { } @Override - public synchronized List autoPurgeDatabase(long until) { - List list = new ArrayList<>(); + public Set autoPurgeDatabase(long until) { + Set list = new HashSet<>(); String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_LOGIN + " getAllAuthsByIp(String ip) { + public List getAllAuthsByIp(String ip) { List result = new ArrayList<>(); String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.IP + "=?;"; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { @@ -720,7 +726,7 @@ public class MySQL implements DataSource { } @Override - public synchronized int countAuthsByEmail(String email) { + public int countAuthsByEmail(String email) { String sql = "SELECT COUNT(1) FROM " + tableName + " WHERE UPPER(" + col.EMAIL + ") = UPPER(?)"; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { pst.setString(1, email); @@ -736,7 +742,7 @@ public class MySQL implements DataSource { } @Override - public synchronized void purgeBanned(List banned) { + public void purgeBanned(Set banned) { String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { for (String name : banned) { diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index dfc74740..8072d58f 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -1,14 +1,5 @@ package fr.xephi.authme.datasource; -import com.google.common.annotations.VisibleForTesting; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.settings.properties.DatabaseSettings; -import fr.xephi.authme.util.StringUtils; - import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; @@ -16,7 +7,19 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; + +import com.google.common.annotations.VisibleForTesting; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.util.StringUtils; /** */ @@ -61,14 +64,13 @@ public class SQLite implements DataSource { ConsoleLogger.logException("Error while executing SQL statement:", e); } - private synchronized void connect() throws ClassNotFoundException, SQLException { + private void connect() throws ClassNotFoundException, SQLException { Class.forName("org.sqlite.JDBC"); ConsoleLogger.info("SQLite driver loaded"); this.con = DriverManager.getConnection("jdbc:sqlite:plugins/AuthMe/" + database + ".db"); - } - private synchronized void setup() throws SQLException { + private void setup() throws SQLException { Statement st = null; ResultSet rs = null; try { @@ -131,11 +133,17 @@ public class SQLite implements DataSource { @Override public void reload() { - // TODO 20160309: Implement reloading + close(con); + try { + this.connect(); + this.setup(); + } catch (ClassNotFoundException | SQLException ex) { + ConsoleLogger.logException("Error during SQLite initialization:", ex); + } } @Override - public synchronized boolean isAuthAvailable(String user) { + public boolean isAuthAvailable(String user) { PreparedStatement pst = null; ResultSet rs = null; try { @@ -173,7 +181,7 @@ public class SQLite implements DataSource { } @Override - public synchronized PlayerAuth getAuth(String user) { + public PlayerAuth getAuth(String user) { PreparedStatement pst = null; ResultSet rs = null; try { @@ -193,7 +201,7 @@ public class SQLite implements DataSource { } @Override - public synchronized boolean saveAuth(PlayerAuth auth) { + public boolean saveAuth(PlayerAuth auth) { PreparedStatement pst = null; try { HashedPassword password = auth.getPassword(); @@ -234,7 +242,7 @@ public class SQLite implements DataSource { } @Override - public synchronized boolean updatePassword(PlayerAuth auth) { + public boolean updatePassword(PlayerAuth auth) { return updatePassword(auth.getNickname(), auth.getPassword()); } @@ -285,8 +293,8 @@ public class SQLite implements DataSource { } @Override - public List autoPurgeDatabase(long until) { - List list = new ArrayList<>(); + public Set autoPurgeDatabase(long until) { + Set list = new HashSet<>(); String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_LOGIN + " banned) { + public void purgeBanned(Set banned) { String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; try (PreparedStatement pst = con.prepareStatement(sql)) { for (String name : banned) { diff --git a/src/main/java/fr/xephi/authme/events/AbstractTeleportEvent.java b/src/main/java/fr/xephi/authme/events/AbstractTeleportEvent.java index 57a456b6..51d366d9 100644 --- a/src/main/java/fr/xephi/authme/events/AbstractTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/AbstractTeleportEvent.java @@ -19,25 +19,13 @@ public abstract class AbstractTeleportEvent extends CustomEvent implements Cance * * @param isAsync Whether to fire the event asynchronously or not * @param player The player - * @param from The location the player is being teleported away from - * @param to The teleport destination - */ - public AbstractTeleportEvent(boolean isAsync, Player player, Location from, Location to) { - super(isAsync); - this.player = player; - this.from = from; - this.to = to; - } - - /** - * Constructor, using the player's current location as "from" location. - * - * @param isAsync Whether to fire the event asynchronously or not - * @param player The player * @param to The teleport destination */ public AbstractTeleportEvent(boolean isAsync, Player player, Location to) { - this(isAsync, player, player.getLocation(), to); + super(isAsync); + this.player = player; + this.from = player.getLocation(); + this.to = to; } /** diff --git a/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java b/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java index b2a5819a..843cfaf2 100644 --- a/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java +++ b/src/main/java/fr/xephi/authme/events/AuthMeAsyncPreLoginEvent.java @@ -5,7 +5,8 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; /** - * This event is called when a player uses the /login command with correct credentials. + * This event is called when a player uses the login command, + * it's fired even when a user does a /login with invalid password. * {@link #setCanLogin(boolean) event.setCanLogin(false)} prevents the player from logging in. */ public class AuthMeAsyncPreLoginEvent extends CustomEvent { diff --git a/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java b/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java index 28532b39..e5e6868d 100644 --- a/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/FirstSpawnTeleportEvent.java @@ -17,11 +17,10 @@ public class FirstSpawnTeleportEvent extends AbstractTeleportEvent { * Constructor. * * @param player The player - * @param from The location the player is being teleported away from * @param to The teleport destination */ - public FirstSpawnTeleportEvent(Player player, Location from, Location to) { - super(true, player, from, to); + public FirstSpawnTeleportEvent(Player player, Location to) { + super(false, player, to); } /** diff --git a/src/main/java/fr/xephi/authme/events/LoginEvent.java b/src/main/java/fr/xephi/authme/events/LoginEvent.java index 6c2647fd..eb38d626 100644 --- a/src/main/java/fr/xephi/authme/events/LoginEvent.java +++ b/src/main/java/fr/xephi/authme/events/LoginEvent.java @@ -30,6 +30,17 @@ public class LoginEvent extends CustomEvent { return player; } + /** + * Ensures compatibility with plugins like GuiRules. + * + * @return true + * @deprecated this will always return true because this event is only called if it was successful + */ + @Deprecated + public boolean isLogin() { + return true; + } + /** * Return the list of handlers, equivalent to {@link #getHandlers()} and required by {@link Event}. * diff --git a/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java b/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java index 3a429bc3..a1a85d84 100644 --- a/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java +++ b/src/main/java/fr/xephi/authme/events/SpawnTeleportEvent.java @@ -17,12 +17,11 @@ public class SpawnTeleportEvent extends AbstractTeleportEvent { * Constructor. * * @param player The player - * @param from The location the player is being teleported away from * @param to The teleport destination * @param isAuthenticated Whether or not the player is logged in */ - public SpawnTeleportEvent(Player player, Location from, Location to, boolean isAuthenticated) { - super(false, player, from, to); + public SpawnTeleportEvent(Player player, Location to, boolean isAuthenticated) { + super(false, player, to); this.isAuthenticated = isAuthenticated; } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index ed089b8a..47e7d17d 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -2,29 +2,37 @@ package fr.xephi.authme.hooks; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; + import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.BukkitService; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; -/** - */ +import javax.inject.Inject; + + public class BungeeCordMessage implements PluginMessageListener { - private final AuthMe plugin; + @Inject + private DataSource dataSource; + + @Inject + private BukkitService bukkitService; + + @Inject + private PlayerCache playerCache; + + @Inject + private AuthMe plugin; + + BungeeCordMessage() { } - /** - * Constructor for BungeeCordMessage. - * - * @param plugin The plugin instance - */ - public BungeeCordMessage(AuthMe plugin) { - this.plugin = plugin; - } @Override public void onPluginMessageReceived(String channel, Player player, byte[] message) { @@ -38,8 +46,7 @@ public class BungeeCordMessage implements PluginMessageListener { final String[] args = str.split(";"); final String act = args[0]; final String name = args[1]; - final DataSource dataSource = plugin.getDataSource(); - plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + bukkitService.runTaskAsynchronously(new Runnable() { @Override public void run() { PlayerAuth auth = dataSource.getAuth(name); @@ -47,23 +54,33 @@ public class BungeeCordMessage implements PluginMessageListener { return; } if ("login".equals(act)) { - PlayerCache.getInstance().updatePlayer(auth); + playerCache.updatePlayer(auth); dataSource.setLogged(name); - ConsoleLogger.info("Player " + auth.getNickname() - + " has logged in from one of your server!"); + //START 03062016 sgdc3: should fix #731 but we need to recode this mess + if (plugin.sessions.containsKey(name)) { + plugin.sessions.get(name).cancel(); + plugin.sessions.remove(name); + } + //END + + if (!plugin.getSettings().getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { + ConsoleLogger.info("Player " + auth.getNickname() + " has logged in from one of your server!"); + } } else if ("logout".equals(act)) { - PlayerCache.getInstance().removePlayer(name); + playerCache.removePlayer(name); dataSource.setUnlogged(name); - ConsoleLogger.info("Player " + auth.getNickname() - + " has logged out from one of your server!"); + if (!plugin.getSettings().getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { + ConsoleLogger.info("Player " + auth.getNickname() + " has logged out from one of your server!"); + } } else if ("register".equals(act)) { - ConsoleLogger.info("Player " + auth.getNickname() - + " has registered out from one of your server!"); + if (!plugin.getSettings().getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { + ConsoleLogger.info("Player " + auth.getNickname() + " has registered out from one of your server!"); + } } else if ("changepassword".equals(act)) { final String password = args[2]; final String salt = args.length >= 4 ? args[3] : null; auth.setPassword(new HashedPassword(password, salt)); - PlayerCache.getInstance().updatePlayer(auth); + playerCache.updatePlayer(auth); dataSource.updatePassword(auth); } diff --git a/src/main/java/fr/xephi/authme/hooks/PluginHooks.java b/src/main/java/fr/xephi/authme/hooks/PluginHooks.java index fd455bb1..76f8e034 100644 --- a/src/main/java/fr/xephi/authme/hooks/PluginHooks.java +++ b/src/main/java/fr/xephi/authme/hooks/PluginHooks.java @@ -1,19 +1,18 @@ package fr.xephi.authme.hooks; -import java.io.File; - +import com.earth2me.essentials.Essentials; +import com.onarandombox.MultiverseCore.MultiverseCore; +import com.onarandombox.MultiverseCore.api.MVWorldManager; +import fr.xephi.authme.ConsoleLogger; +import net.minelink.ctplus.CombatTagPlus; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import com.earth2me.essentials.Essentials; -import com.onarandombox.MultiverseCore.MultiverseCore; -import com.onarandombox.MultiverseCore.api.MVWorldManager; - -import fr.xephi.authme.ConsoleLogger; -import net.minelink.ctplus.CombatTagPlus; +import javax.inject.Inject; +import java.io.File; /** * Hooks into third-party plugins and allows to perform actions on them. @@ -30,6 +29,7 @@ public class PluginHooks { * * @param pluginManager The server's plugin manager */ + @Inject public PluginHooks(PluginManager pluginManager) { this.pluginManager = pluginManager; tryHookToCombatPlus(); @@ -77,13 +77,23 @@ public class PluginHooks { return null; } + /** + * Checks whether the player is an NPC. + * + * @param player The player to process + * @return True if player is NPC, false otherwise + */ + public boolean isNpc(Player player) { + return player.hasMetadata("NPC") || isNpcInCombatTagPlus(player); + } + /** * Query the CombatTagPlus plugin whether the given player is an NPC. * * @param player The player to verify * @return True if the player is an NPC according to CombatTagPlus, false if not or if the plugin is unavailable */ - public boolean isNpcInCombatTagPlus(Player player) { + private boolean isNpcInCombatTagPlus(Player player) { return combatTagPlus != null && combatTagPlus.getNpcPlayerHelper().isNpc(player); } diff --git a/src/main/java/fr/xephi/authme/initialization/AuthMeServiceInitializer.java b/src/main/java/fr/xephi/authme/initialization/AuthMeServiceInitializer.java new file mode 100644 index 00000000..29e5adcf --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/AuthMeServiceInitializer.java @@ -0,0 +1,324 @@ +package fr.xephi.authme.initialization; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import fr.xephi.authme.settings.NewSetting; + +import javax.annotation.PostConstruct; +import javax.inject.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Dependency injector of AuthMe: initializes and injects services and tasks. + *

+ * Only constructor and field injection are supported, indicated with the JSR330 + * {@link javax.inject.Inject @Inject} annotation. + *

+ * {@link PostConstruct @PostConstruct} methods are recognized and invoked upon + * instantiation. Note that the parent classes are not scanned for such methods. + */ +public class AuthMeServiceInitializer { + + private final Set ALLOWED_PACKAGES; + private final Map, Object> objects; + + /** + * Constructor. + * + * @param allowedPackages list of allowed packages. Only classes whose package + * starts with any of the given entries will be instantiated + */ + public AuthMeServiceInitializer(String... allowedPackages) { + ALLOWED_PACKAGES = ImmutableSet.copyOf(allowedPackages); + objects = new HashMap<>(); + objects.put(getClass(), this); + } + + /** + * Retrieves or instantiates an object of the given type. + * + * @param clazz the class to retrieve the value for + * @param the class' type + * @return object of the class' type + */ + public T get(Class clazz) { + return get(clazz, new HashSet>()); + } + + /** + * Register an object with a custom class (supertype). Use this for example to specify a + * concrete implementation of an interface or an abstract class. + * + * @param clazz the class to register the object for + * @param object the object + * @param the class' type + */ + public void register(Class clazz, T object) { + if (objects.containsKey(clazz)) { + throw new IllegalStateException("There is already an object present for " + clazz); + } + Preconditions.checkNotNull(object); + objects.put(clazz, object); + } + + /** + * Associate an annotation with a value. + * + * @param annotation the annotation + * @param value the value + */ + public void provide(Class annotation, Object value) { + if (objects.containsKey(annotation)) { + throw new IllegalStateException("Annotation @" + annotation.getClass().getSimpleName() + + " already registered"); + } + Preconditions.checkNotNull(value); + objects.put(annotation, value); + } + + /** + * Creates a new instance of the given class and does not keep track of it afterwards, + * unlike {@link #get(Class)} (singleton scope). + * + * @param clazz the class to instantiate + * @param the class' type + * @return new instance of class T + */ + public T newInstance(Class clazz) { + return instantiate(clazz, new HashSet>()); + } + + /** + * Returns an instance of the given class if available. This simply returns the instance if present and + * otherwise {@code null}. Calling this method will not instantiate anything. + * + * @param clazz the class to retrieve the instance for + * @param the class' type + * @return instance or null if none available + */ + public T getIfAvailable(Class clazz) { + if (Annotation.class.isAssignableFrom(clazz)) { + throw new UnsupportedOperationException("Annotations may not be retrieved in this way!"); + } + return clazz.cast(objects.get(clazz)); + } + + /** + * Returns an instance of the given class by retrieving it or by instantiating it if not yet present. + * + * @param clazz the class to retrieve a value for + * @param traversedClasses the list of traversed classes + * @param the class' type + * @return instance or associated value (for annotations) + */ + private T get(Class clazz, Set> traversedClasses) { + if (Annotation.class.isAssignableFrom(clazz)) { + throw new UnsupportedOperationException("Cannot retrieve annotated elements in this way!"); + } else if (objects.containsKey(clazz)) { + return clazz.cast(objects.get(clazz)); + } + + // First time we come across clazz, need to instantiate it. Validate that we can do so + validatePackage(clazz); + validateInstantiable(clazz); + + // Add the clazz to the list of traversed classes in a new Set, so each path we take has its own Set. + traversedClasses = new HashSet<>(traversedClasses); + traversedClasses.add(clazz); + T object = instantiate(clazz, traversedClasses); + storeObject(object); + return object; + } + + /** + * Performs a reload on all applicable instances which are registered. + * Requires that the {@link NewSetting settings} instance be registered. + *

+ * Note that the order in which these classes are reloaded is not guaranteed. + */ + public void performReloadOnServices() { + NewSetting settings = (NewSetting) objects.get(NewSetting.class); + if (settings == null) { + throw new IllegalStateException("Settings instance is null"); + } + for (Object object : objects.values()) { + if (object instanceof Reloadable) { + ((Reloadable) object).reload(); + } else if (object instanceof SettingsDependent) { + ((SettingsDependent) object).loadSettings(settings); + } + } + } + + /** + * Instantiates the given class by locating its @Inject elements and retrieving + * or instantiating the required instances. + * + * @param clazz the class to instantiate + * @param traversedClasses collection of classes already traversed + * @param the class' type + * @return the instantiated object + */ + private T instantiate(Class clazz, Set> traversedClasses) { + Injection injection = firstNotNull( + ConstructorInjection.provide(clazz), FieldInjection.provide(clazz), InstantiationFallback.provide(clazz)); + if (injection == null) { + throw new IllegalStateException("Did not find injection method for " + clazz + ". Make sure you have " + + "a constructor with @Inject or fields with @Inject. Fields with @Inject require " + + "the default constructor"); + } + + validateInjectionHasNoCircularDependencies(injection.getDependencies(), traversedClasses); + Object[] dependencies = resolveDependencies(injection, traversedClasses); + T object = injection.instantiateWith(dependencies); + executePostConstructMethod(object); + return object; + } + + /** + * Resolves the dependencies for the given class instantiation, i.e. returns a collection that satisfy + * the class' dependencies by retrieving elements or instantiating them where necessary. + * + * @param injection the injection parameters + * @param traversedClasses collection of traversed classes + * @return array with the parameters to use in the constructor + */ + private Object[] resolveDependencies(Injection injection, Set> traversedClasses) { + Class[] dependencies = injection.getDependencies(); + Class[] annotations = injection.getDependencyAnnotations(); + Object[] values = new Object[dependencies.length]; + for (int i = 0; i < dependencies.length; ++i) { + if (annotations[i] == null) { + values[i] = get(dependencies[i], traversedClasses); + } else { + Object value = objects.get(annotations[i]); + if (value == null) { + throw new IllegalStateException("Value for field with @" + annotations[i].getSimpleName() + + " must be registered beforehand"); + } + values[i] = value; + } + } + return values; + } + + /** + * Stores the given object with its class as key. Throws an exception if the key already has + * a value associated to it. + * + * @param object the object to store + */ + private void storeObject(Object object) { + if (objects.containsKey(object.getClass())) { + throw new IllegalStateException("There is already an object present for " + object.getClass()); + } + Preconditions.checkNotNull(object); + objects.put(object.getClass(), object); + } + + /** + * Validates that none of the dependencies' types are present in the given collection + * of traversed classes. This prevents circular dependencies. + * + * @param dependencies the dependencies of the class + * @param traversedClasses the collection of traversed classes + */ + private static void validateInjectionHasNoCircularDependencies(Class[] dependencies, + Set> traversedClasses) { + for (Class clazz : dependencies) { + if (traversedClasses.contains(clazz)) { + throw new IllegalStateException("Found cyclic dependency - already traversed '" + clazz + + "' (full traversal list: " + traversedClasses + ")"); + } + } + } + + /** + * Validates the package of a parameter type to ensure that it is part of the allowed packages. + * This ensures that we don't try to instantiate things that are beyond our reach in case some + * external parameter type has not been registered. + * + * @param clazz the class to validate + */ + private void validatePackage(Class clazz) { + if (clazz.getPackage() == null) { + throw new IllegalStateException("Primitive types must be provided explicitly (or use an annotation)."); + } + String packageName = clazz.getPackage().getName(); + for (String allowedPackage : ALLOWED_PACKAGES) { + if (packageName.startsWith(allowedPackage)) { + return; + } + } + throw new IllegalStateException("Class " + clazz + " with package " + packageName + " is outside of the " + + "allowed packages. It must be provided explicitly or the package must be passed to the constructor."); + } + + /** + * Executes an object's method annotated with {@link PostConstruct} if present. + * Throws an exception if there are multiple such methods, or if the method is static. + * + * @param object the object to execute the post construct method for + */ + private static void executePostConstructMethod(Object object) { + Method postConstructMethod = getAndValidatePostConstructMethod(object.getClass()); + if (postConstructMethod != null) { + try { + postConstructMethod.setAccessible(true); + postConstructMethod.invoke(object); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new UnsupportedOperationException("Error executing @PostConstruct method", e); + } + } + } + + private static void validateInstantiable(Class clazz) { + if (clazz.isEnum() || clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { + throw new IllegalStateException("Class " + clazz.getSimpleName() + " cannot be instantiated"); + } + } + + /** + * Validate and locate the given class' post construct method. Returns {@code null} if none present. + * + * @param clazz the class to search + * @return post construct method, or null + */ + private static Method getAndValidatePostConstructMethod(Class clazz) { + Method postConstructMethod = null; + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(PostConstruct.class)) { + if (postConstructMethod != null) { + throw new IllegalStateException("Multiple methods with @PostConstruct on " + clazz); + } else if (method.getParameterTypes().length > 0 || Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException("@PostConstruct method may not be static or have any parameters. " + + "Invalid method in " + clazz); + } else if (method.getReturnType() != void.class) { + throw new IllegalStateException("@PostConstruct method must have return type void. " + + "Offending class: " + clazz); + } else { + postConstructMethod = method; + } + } + } + return postConstructMethod; + } + + @SafeVarargs + private static Injection firstNotNull(Provider>... providers) { + for (Provider> provider : providers) { + Injection object = provider.get(); + if (object != null) { + return object; + } + } + return null; + } +} diff --git a/src/main/java/fr/xephi/authme/initialization/ConstructorInjection.java b/src/main/java/fr/xephi/authme/initialization/ConstructorInjection.java new file mode 100644 index 00000000..74ea28b2 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/ConstructorInjection.java @@ -0,0 +1,86 @@ +package fr.xephi.authme.initialization; + +import com.google.common.base.Preconditions; + +import javax.inject.Inject; +import javax.inject.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Functionality for constructor injection. + */ +public class ConstructorInjection implements Injection { + + private final Constructor constructor; + + private ConstructorInjection(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public Class[] getDependencies() { + return constructor.getParameterTypes(); + } + + @Override + public Class[] getDependencyAnnotations() { + Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); + Class[] annotations = new Class[parameterAnnotations.length]; + for (int i = 0; i < parameterAnnotations.length; ++i) { + annotations[i] = parameterAnnotations[i].length > 0 + ? parameterAnnotations[i][0].annotationType() + : null; + } + return annotations; + } + + @Override + public T instantiateWith(Object... values) { + validateNoNullValues(values); + try { + return constructor.newInstance(values); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new UnsupportedOperationException(e); + } + } + + public static Provider> provide(final Class clazz) { + return new Provider>() { + @Override + public ConstructorInjection get() { + Constructor constructor = getInjectionConstructor(clazz); + return constructor == null ? null : new ConstructorInjection<>(constructor); + } + }; + } + + + /** + * Gets the first found constructor annotated with {@link Inject} of the given class + * and marks it as accessible. + * + * @param clazz the class to process + * @param the class' type + * @return injection constructor for the class, null if not applicable + */ + @SuppressWarnings("unchecked") + private static Constructor getInjectionConstructor(Class clazz) { + Constructor[] constructors = clazz.getDeclaredConstructors(); + for (Constructor constructor : constructors) { + if (constructor.isAnnotationPresent(Inject.class)) { + constructor.setAccessible(true); + return (Constructor) constructor; + } + } + return null; + } + + private static void validateNoNullValues(Object[] array) { + for (Object entry : array) { + Preconditions.checkNotNull(entry); + } + } + +} diff --git a/src/main/java/fr/xephi/authme/initialization/DataFolder.java b/src/main/java/fr/xephi/authme/initialization/DataFolder.java new file mode 100644 index 00000000..0288f45a --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/DataFolder.java @@ -0,0 +1,14 @@ +package fr.xephi.authme.initialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for specifying the plugin's data folder. + */ +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DataFolder { +} diff --git a/src/main/java/fr/xephi/authme/initialization/FieldInjection.java b/src/main/java/fr/xephi/authme/initialization/FieldInjection.java new file mode 100644 index 00000000..c74e7c27 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/FieldInjection.java @@ -0,0 +1,128 @@ +package fr.xephi.authme.initialization; + +import com.google.common.base.Preconditions; + +import javax.inject.Inject; +import javax.inject.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Functionality for field injection. + */ +public class FieldInjection implements Injection { + + private final Field[] fields; + private final Constructor defaultConstructor; + + private FieldInjection(Constructor defaultConstructor, Collection fields) { + this.fields = fields.toArray(new Field[fields.size()]); + this.defaultConstructor = defaultConstructor; + } + + @Override + public Class[] getDependencies() { + Class[] types = new Class[fields.length]; + for (int i = 0; i < fields.length; ++i) { + types[i] = fields[i].getType(); + } + return types; + } + + @Override + public Class[] getDependencyAnnotations() { + Class[] annotations = new Class[fields.length]; + for (int i = 0; i < fields.length; ++i) { + annotations[i] = getFirstNonInjectAnnotation(fields[i]); + } + return annotations; + } + + @Override + public T instantiateWith(Object... values) { + Preconditions.checkArgument(values.length == fields.length, + "The number of values must be equal to the number of fields"); + + T instance; + try { + instance = defaultConstructor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new UnsupportedOperationException(e); + } + + for (int i = 0; i < fields.length; ++i) { + try { + Preconditions.checkNotNull(values[i]); + fields[i].set(instance, values[i]); + } catch (IllegalAccessException e) { + throw new UnsupportedOperationException(e); + } + } + return instance; + } + + /** + * Returns a provider for a {@code FieldInjection} instance, i.e. a provides an object + * with which field injection can be performed on the given class if applicable. The provided + * value is {@code null} if field injection cannot be applied to the class. + * + * @param clazz the class to provide field injection for + * @param the class' type + * @return field injection provider for the given class, or null if not applicable + */ + public static Provider> provide(final Class clazz) { + return new Provider>() { + @Override + public FieldInjection get() { + Constructor constructor = getDefaultConstructor(clazz); + if (constructor == null) { + return null; + } + List fields = getInjectionFields(clazz); + return fields.isEmpty() ? null : new FieldInjection<>(constructor, fields); + } + }; + } + + private static List getInjectionFields(Class clazz) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class)) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalStateException(String.format("Field '%s' in class '%s' is static but " + + "annotated with @Inject", field.getName(), clazz.getSimpleName())); + } + field.setAccessible(true); + fields.add(field); + } + } + return fields; + } + + private static Class getFirstNonInjectAnnotation(Field field) { + for (Annotation annotation : field.getAnnotations()) { + if (annotation.annotationType() != Inject.class) { + return annotation.annotationType(); + } + } + return null; + } + + @SuppressWarnings("unchecked") + private static Constructor getDefaultConstructor(Class clazz) { + try { + Constructor defaultConstructor = clazz.getDeclaredConstructor(); + defaultConstructor.setAccessible(true); + return (Constructor) defaultConstructor; + } catch (NoSuchMethodException ignore) { + // no default constructor available + } + return null; + } +} diff --git a/src/main/java/fr/xephi/authme/initialization/Injection.java b/src/main/java/fr/xephi/authme/initialization/Injection.java new file mode 100644 index 00000000..65acf796 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/Injection.java @@ -0,0 +1,36 @@ +package fr.xephi.authme.initialization; + +/** + * Common interface for all injection methods. + * + * @param the type of the concerned object + */ +public interface Injection { + + /** + * Returns the dependencies that must be provided to instantiate the given item. + * + * @return list of dependencies + * @see #instantiateWith + */ + Class[] getDependencies(); + + /** + * Returns the annotation on each dependency if available. The indices of this + * array correspond to the ones of {@link #getDependencies()}. If no annotation + * is available, {@code null} is stored. If multiple annotations are present, only + * one is stored (no guarantee on which one). + * + * @return annotation for each dependency + */ + Class[] getDependencyAnnotations(); + + /** + * Creates a new instance with the given values as dependencies. The given values + * must correspond to {@link #getDependencies()} in size, order and type. + * + * @param values the values to set for the dependencies + * @return resulting object + */ + T instantiateWith(Object... values); +} diff --git a/src/main/java/fr/xephi/authme/initialization/InstantiationFallback.java b/src/main/java/fr/xephi/authme/initialization/InstantiationFallback.java new file mode 100644 index 00000000..7a1c0849 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/InstantiationFallback.java @@ -0,0 +1,85 @@ +package fr.xephi.authme.initialization; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Provider; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Fallback instantiation method for classes with an accessible no-args constructor + * and no elements whatsoever annotated with {@link Inject} or {@link PostConstruct}. + */ +public class InstantiationFallback implements Injection { + + private final Constructor constructor; + + private InstantiationFallback(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public Class[] getDependencies() { + return new Class[0]; + } + + @Override + public Class[] getDependencyAnnotations() { + return new Class[0]; + } + + @Override + public T instantiateWith(Object... values) { + if (values == null || values.length > 0) { + throw new UnsupportedOperationException("Instantiation fallback cannot have parameters"); + } + try { + return constructor.newInstance(); + } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) { + throw new UnsupportedOperationException(e); + } + } + + /** + * Returns an instantiation fallback if the class is applicable. + * + * @param clazz the class + * @param the class' type + * @return instantiation fallback provider for the given class, or null if not applicable + */ + public static Provider> provide(final Class clazz) { + return new Provider>() { + @Override + public InstantiationFallback get() { + Constructor noArgsConstructor = getNoArgsConstructor(clazz); + // Return fallback only if we have no args constructor and no @Inject annotation anywhere + if (noArgsConstructor != null + && !isInjectionAnnotationPresent(clazz.getDeclaredConstructors()) + && !isInjectionAnnotationPresent(clazz.getDeclaredFields()) + && !isInjectionAnnotationPresent(clazz.getDeclaredMethods())) { + return new InstantiationFallback<>(noArgsConstructor); + } + return null; + } + }; + } + + private static Constructor getNoArgsConstructor(Class clazz) { + try { + // Note ljacqu 20160504: getConstructor(), unlike getDeclaredConstructor(), only considers public members + return clazz.getConstructor(); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static boolean isInjectionAnnotationPresent(A[] accessibles) { + for (A accessible : accessibles) { + if (accessible.isAnnotationPresent(Inject.class) || accessible.isAnnotationPresent(PostConstruct.class)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/MetricsStarter.java b/src/main/java/fr/xephi/authme/initialization/MetricsStarter.java similarity index 93% rename from src/main/java/fr/xephi/authme/MetricsStarter.java rename to src/main/java/fr/xephi/authme/initialization/MetricsStarter.java index eac9ebaf..4a163f98 100644 --- a/src/main/java/fr/xephi/authme/MetricsStarter.java +++ b/src/main/java/fr/xephi/authme/initialization/MetricsStarter.java @@ -1,5 +1,7 @@ -package fr.xephi.authme; +package fr.xephi.authme.initialization; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.PluginSettings; diff --git a/src/main/java/fr/xephi/authme/initialization/Reloadable.java b/src/main/java/fr/xephi/authme/initialization/Reloadable.java new file mode 100644 index 00000000..6b28fc7d --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/Reloadable.java @@ -0,0 +1,13 @@ +package fr.xephi.authme.initialization; + +/** + * Interface for reloadable entities. + */ +public interface Reloadable { + + /** + * Performs the reload action. + */ + void reload(); + +} diff --git a/src/main/java/fr/xephi/authme/initialization/SettingsDependent.java b/src/main/java/fr/xephi/authme/initialization/SettingsDependent.java new file mode 100644 index 00000000..f891b170 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/SettingsDependent.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.initialization; + +import fr.xephi.authme.settings.NewSetting; + +/** + * Interface for classes that keep a local copy of certain settings. + */ +public interface SettingsDependent { + + /** + * Loads the needed settings. + * + * @param settings the settings instance + */ + void loadSettings(NewSetting settings); +} diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java index 9ffdd483..7fe23dd9 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeBlockListener.java @@ -5,18 +5,23 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; +import javax.inject.Inject; + public class AuthMeBlockListener implements Listener { + @Inject + private ListenerService listenerService; + @EventHandler(ignoreCancelled = true) public void onBlockPlace(BlockPlaceEvent event) { - if (ListenerService.shouldCancelEvent(event.getPlayer())) { + if (listenerService.shouldCancelEvent(event.getPlayer())) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { - if (ListenerService.shouldCancelEvent(event.getPlayer())) { + if (listenerService.shouldCancelEvent(event.getPlayer())) { event.setCancelled(true); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java index 897607ee..ce8afa40 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java @@ -1,5 +1,6 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.ConsoleLogger; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; @@ -14,29 +15,32 @@ import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.projectiles.ProjectileSource; +import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent; - public class AuthMeEntityListener implements Listener { - private static Method getShooter; - private static boolean shooterIsProjectileSource; + private final ListenerService listenerService; + private Method getShooter; + private boolean shooterIsLivingEntity; - public AuthMeEntityListener() { + @Inject + AuthMeEntityListener(ListenerService listenerService) { + this.listenerService = listenerService; try { - Method m = Projectile.class.getDeclaredMethod("getShooter"); - shooterIsProjectileSource = m.getReturnType() != LivingEntity.class; - } catch (Exception ignored) { + getShooter = Projectile.class.getDeclaredMethod("getShooter"); + shooterIsLivingEntity = getShooter.getReturnType() == LivingEntity.class; + } catch (NoSuchMethodException | SecurityException e) { + ConsoleLogger.logException("Cannot load getShooter() method on Projectile class", e); } } // Note #360: npc status can be used to bypass security!!! @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onEntityDamage(EntityDamageEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.getEntity().setFireTicks(0); event.setDamage(0); event.setCancelled(true); @@ -45,7 +49,7 @@ public class AuthMeEntityListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onEntityTarget(EntityTargetEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setTarget(null); event.setCancelled(true); } @@ -53,21 +57,21 @@ public class AuthMeEntityListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onDamage(EntityDamageByEntityEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onFoodLevelChange(FoodLevelChangeEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void entityRegainHealthEvent(EntityRegainHealthEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setAmount(0); event.setCancelled(true); } @@ -75,53 +79,48 @@ public class AuthMeEntityListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onEntityInteract(EntityInteractEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onLowestEntityInteract(EntityInteractEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } - // TODO #568: Need to check this, player can't throw snowball but the item is taken. + // TODO #733: Player can't throw snowball but the item is taken. @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onProjectileLaunch(ProjectileLaunchEvent event) { if (event.getEntity() == null) { return; } - Player player = null; Projectile projectile = event.getEntity(); - if (shooterIsProjectileSource) { - ProjectileSource shooter = projectile.getShooter(); - if (shooter == null || !(shooter instanceof Player)) { - return; - } - player = (Player) shooter; - } else { - // TODO #568 20151220: Invoking getShooter() with null but method isn't static + // In the Bukkit API prior to 1.7, getShooter() returns a LivingEntity instead of a ProjectileSource + Object shooterRaw = null; + if (shooterIsLivingEntity) { try { if (getShooter == null) { getShooter = Projectile.class.getMethod("getShooter"); } - Object obj = getShooter.invoke(null); - player = (Player) obj; - } catch (Exception ignored) { + shooterRaw = getShooter.invoke(projectile); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + ConsoleLogger.logException("Error getting shooter", e); } + } else { + shooterRaw = projectile.getShooter(); } - - if (ListenerService.shouldCancelEvent(player)) { + if (shooterRaw instanceof Player && listenerService.shouldCancelEvent((Player) shooterRaw)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) public void onShoot(EntityShootBowEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index e3753793..84fff695 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -1,32 +1,18 @@ package fr.xephi.authme.listener; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AntiBot.AntiBotStatus; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.ValidationService; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -54,11 +40,12 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerShearEntityEvent; +import javax.inject.Inject; +import java.util.Iterator; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS; -import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT; /** @@ -67,107 +54,65 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAU public class AuthMePlayerListener implements Listener { public static final ConcurrentHashMap joinMessage = new ConcurrentHashMap<>(); - public static final ConcurrentHashMap causeByAuthMe = new ConcurrentHashMap<>(); - private final AuthMe plugin; - private final NewSetting settings; - private final Messages m; - private final DataSource dataSource; - private final AntiBot antiBot; - private final Management management; - private final BukkitService bukkitService; - private final ValidationService validationService; - public AuthMePlayerListener(AuthMe plugin, NewSetting settings, Messages messages, DataSource dataSource, - AntiBot antiBot, Management management, BukkitService bukkitService, - ValidationService validationService) { - this.plugin = plugin; - this.settings = settings; - this.m = messages; - this.dataSource = dataSource; - this.antiBot = antiBot; - this.management = management; - this.bukkitService = bukkitService; - this.validationService = validationService; - } - - private void handleChat(AsyncPlayerChatEvent event) { - if (settings.getProperty(RestrictionSettings.ALLOW_CHAT)) { - return; - } - - final Player player = event.getPlayer(); - if (shouldCancelEvent(player)) { - event.setCancelled(true); - sendLoginOrRegisterMessage(player); - } else if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) { - for (Player p : bukkitService.getOnlinePlayers()) { - if (!PlayerCache.getInstance().isAuthenticated(p.getName())) { - event.getRecipients().remove(p); - } - } - } - } - - private void sendLoginOrRegisterMessage(final Player player) { - bukkitService.runTaskAsynchronously(new Runnable() { - @Override - public void run() { - if (dataSource.isAuthAvailable(player.getName().toLowerCase())) { - m.send(player, MessageKey.LOGIN_MESSAGE); - } else { - if (settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)) { - m.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); - } else { - m.send(player, MessageKey.REGISTER_MESSAGE); - } - } - } - }); - } + @Inject + private NewSetting settings; + @Inject + private Messages m; + @Inject + private DataSource dataSource; + @Inject + private AntiBot antiBot; + @Inject + private Management management; + @Inject + private BukkitService bukkitService; + @Inject + private SpawnLoader spawnLoader; + @Inject + private OnJoinVerifier onJoinVerifier; + @Inject + private ListenerService listenerService; @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { String cmd = event.getMessage().split(" ")[0].toLowerCase(); - if (settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD) && "/motd".equals(cmd)) { - return; - } - if (!settings.getProperty(RegistrationSettings.FORCE) - && settings.getProperty(ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL)) { + if (settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD) && cmd.equals("/motd")) { return; } if (settings.getProperty(RestrictionSettings.ALLOW_COMMANDS).contains(cmd)) { return; } - if (Utils.checkAuth(event.getPlayer())) { - return; + final Player player = event.getPlayer(); + if (listenerService.shouldCancelEvent(player)) { + event.setCancelled(true); + m.send(player, MessageKey.DENIED_COMMAND); } - event.setCancelled(true); - sendLoginOrRegisterMessage(event.getPlayer()); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) - public void onPlayerNormalChat(AsyncPlayerChatEvent event) { - handleChat(event); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) - public void onPlayerHighChat(AsyncPlayerChatEvent event) { - handleChat(event); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void onPlayerHighestChat(AsyncPlayerChatEvent event) { - handleChat(event); } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerEarlyChat(AsyncPlayerChatEvent event) { - handleChat(event); - } + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (settings.getProperty(RestrictionSettings.ALLOW_CHAT)) { + return; + } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) - public void onPlayerLowChat(AsyncPlayerChatEvent event) { - handleChat(event); + final Player player = event.getPlayer(); + if (listenerService.shouldCancelEvent(player)) { + event.setCancelled(true); + m.send(player, MessageKey.DENIED_CHAT); + } else if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) { + Set recipients = event.getRecipients(); + Iterator iter = recipients.iterator(); + while (iter.hasNext()) { + Player p = iter.next(); + if (listenerService.shouldCancelEvent(p)) { + iter.remove(); + } + } + if (recipients.size() == 0) { + event.setCancelled(true); + } + } } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -176,24 +121,26 @@ public class AuthMePlayerListener implements Listener { return; } - /* + /* * Limit player X and Z movements to 1 block * Deny player Y+ movements (allows falling) */ - if (event.getFrom().getBlockX() == event.getTo().getBlockX() - && event.getFrom().getBlockZ() == event.getTo().getBlockZ() - && event.getFrom().getY() - event.getTo().getY() >= 0) { + Location from = event.getFrom(); + Location to = event.getTo(); + if (from.getBlockX() == to.getBlockX() + && from.getBlockZ() == to.getBlockZ() + && from.getY() - to.getY() >= 0) { return; } Player player = event.getPlayer(); - if (Utils.checkAuth(player)) { + if (!listenerService.shouldCancelEvent(player)) { return; } if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) { + // "cancel" the event event.setTo(event.getFrom()); - // sgdc3 TODO: remove this, maybe we should set the effect every x ticks, idk! if (settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { player.setFlySpeed(0.0f); player.setWalkSpeed(0.0f); @@ -205,7 +152,7 @@ public class AuthMePlayerListener implements Listener { return; } - Location spawn = plugin.getSpawnLocation(player); + Location spawn = spawnLoader.getSpawnLocation(player); if (spawn != null && spawn.getWorld() != null) { if (!player.getWorld().equals(spawn.getWorld())) { player.teleport(spawn); @@ -245,149 +192,82 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); - if (player == null) { - return; + if (player != null) { + // Schedule login task so works after the prelogin + // (Fix found by Koolaid5000) + bukkitService.runTask(new Runnable() { + @Override + public void run() { + management.performJoin(player); + } + }); } - - if (settings.getProperty(RestrictionSettings.FORCE_SURVIVAL_MODE) - && !player.hasPermission(PlayerStatePermission.BYPASS_FORCE_SURVIVAL.getNode())) { - player.setGameMode(GameMode.SURVIVAL); - } - - // Shedule login task so works after the prelogin - // (Fix found by Koolaid5000) - bukkitService.runTask(new Runnable() { - @Override - public void run() { - management.performJoin(player); - } - }); } + // Note ljacqu 20160528: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode + // e.g. CraftBukkit does not. So we need to run crucial things in onPlayerLogin, too @EventHandler(priority = EventPriority.HIGHEST) public void onPreLogin(AsyncPlayerPreLoginEvent event) { - PlayerAuth auth = dataSource.getAuth(event.getName()); - if (settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE) && auth != null && auth.getRealName() != null) { - String realName = auth.getRealName(); - if (!realName.isEmpty() && !"Player".equals(realName) && !realName.equals(event.getName())) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CASE, realName, event.getName())); - return; - } - if (realName.isEmpty() || "Player".equals(realName)) { - dataSource.updateRealName(event.getName().toLowerCase(), event.getName()); - } - } - - if (auth == null && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { - String playerIp = event.getAddress().getHostAddress(); - if (!validationService.isCountryAdmitted(playerIp)) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); - return; - } - } - final String name = event.getName().toLowerCase(); - final Player player = bukkitService.getPlayerExact(name); - // Check if forceSingleSession is set to true, so kick player that has - // joined with same nick of online player - if (player != null && settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { + final boolean isAuthAvailable = dataSource.isAuthAvailable(event.getName()); + + try { + // Potential performance improvement: make checkAntiBot not require `isAuthAvailable` info and use + // "checkKickNonRegistered" as last -> no need to query the DB before checking antibot / name + onJoinVerifier.checkAntibot(name, isAuthAvailable); + onJoinVerifier.checkKickNonRegistered(isAuthAvailable); + onJoinVerifier.checkIsValidName(name); + // Note #760: Single session must be checked here - checking with PlayerLoginEvent is too late and + // the first connection will have been kicked. This means this feature doesn't work on CraftBukkit. + onJoinVerifier.checkSingleSession(name); + } catch (FailedVerificationException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.USERNAME_ALREADY_ONLINE_ERROR)); - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); - if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { - Utils.addNormal(player, limbo.getGroup()); - LimboCache.getInstance().deleteLimboPlayer(name); - } } } @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerLogin(PlayerLoginEvent event) { final Player player = event.getPlayer(); - if (player == null || Utils.isUnrestricted(player)) { + if (Utils.isUnrestricted(player)) { return; - } - - // Get the permissions manager - PermissionsManager permsMan = plugin.getPermissionsManager(); - - if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL) { - if (permsMan.hasPermission(player, PlayerStatePermission.IS_VIP)) { - int playersOnline = bukkitService.getOnlinePlayers().size(); - if (playersOnline > plugin.getServer().getMaxPlayers()) { - event.allow(); - } else { - Player pl = plugin.generateKickPlayer(bukkitService.getOnlinePlayers()); - if (pl != null) { - pl.kickPlayer(m.retrieveSingle(MessageKey.KICK_FOR_VIP)); - event.allow(); - } else { - ConsoleLogger.info("The player " + event.getPlayer().getName() + " tried to join, but the server was full"); - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - event.setResult(PlayerLoginEvent.Result.KICK_FULL); - } - } - } else { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - event.setResult(PlayerLoginEvent.Result.KICK_FULL); - return; - } - } - - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + } else if (onJoinVerifier.refusePlayerForFullServer(event)) { + return; + } else if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { return; } final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = dataSource.isAuthAvailable(name); + final PlayerAuth auth = dataSource.getAuth(player.getName()); + final boolean isAuthAvailable = (auth != null); - if (antiBot.getAntiBotStatus() == AntiBotStatus.ACTIVE && !isAuthAvailable) { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); + try { + onJoinVerifier.checkAntibot(name, isAuthAvailable); + onJoinVerifier.checkKickNonRegistered(isAuthAvailable); + onJoinVerifier.checkIsValidName(name); + onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkPlayerCountry(isAuthAvailable, event); + } catch (FailedVerificationException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); event.setResult(PlayerLoginEvent.Result.KICK_OTHER); return; } - if (settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED) && !isAuthAvailable) { - event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - if (name.length() > Settings.getMaxNickLength || name.length() < Settings.getMinNickLength) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - if (!Settings.nickPattern.matcher(player.getName()).matches() || name.equalsIgnoreCase("Player")) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CHARACTERS).replace("REG_EX", Settings.getNickRegex)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - antiBot.checkAntiBot(player); - - if (settings.getProperty(HooksSettings.BUNGEECORD)) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("IP"); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } + antiBot.handlePlayerJoin(player); } @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); - if (player == null) { - return; - } - if (settings.getProperty(RegistrationSettings.REMOVE_LEAVE_MESSAGE)) { event.setQuitMessage(null); } + if (antiBot.antibotKicked.contains(player.getName())) { + return; + } + management.performQuit(player, false); } @@ -395,42 +275,28 @@ public class AuthMePlayerListener implements Listener { public void onPlayerKick(PlayerKickEvent event) { Player player = event.getPlayer(); - if (player == null) { - return; + if (!antiBot.antibotKicked.contains(player.getName())) { + management.performQuit(player, true); } - - if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION) - && event.getReason().equals(m.retrieveSingle(MessageKey.USERNAME_ALREADY_ONLINE_ERROR))) { - event.setCancelled(true); - return; - } - - plugin.getManagement().performQuit(player, true); } - /* - * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - * Note #360: npc status can be used to bypass security!!! - * <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - */ - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerPickupItem(PlayerPickupItemEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInteract(PlayerInteractEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerConsumeItem(PlayerItemConsumeEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @@ -439,7 +305,7 @@ public class AuthMePlayerListener implements Listener { public void onPlayerInventoryOpen(InventoryOpenEvent event) { final Player player = (Player) event.getPlayer(); - if (!ListenerService.shouldCancelEvent(player)) { + if (!listenerService.shouldCancelEvent(player)) { return; } event.setCancelled(true); @@ -448,7 +314,7 @@ public class AuthMePlayerListener implements Listener { * @note little hack cause InventoryOpenEvent cannot be cancelled for * real, cause no packet is send to server by client for the main inv */ - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { player.closeInventory(); @@ -458,41 +324,43 @@ public class AuthMePlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInventoryClick(InventoryClickEvent event) { - if (event.getWhoClicked() == null) + if (event.getWhoClicked() == null) { return; - if (!(event.getWhoClicked() instanceof Player)) + } + if (!(event.getWhoClicked() instanceof Player)) { return; - if (Utils.checkAuth((Player) event.getWhoClicked())) - return; - if (Utils.isNPC((Player) event.getWhoClicked())) + } + Player player = (Player) event.getWhoClicked(); + if (!listenerService.shouldCancelEvent(player)) { return; + } event.setCancelled(true); } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerHitPlayerEvent(EntityDamageByEntityEvent event) { - if (ListenerService.shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerDropItem(PlayerDropItemEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerBedEnter(PlayerBedEnterEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @@ -500,21 +368,24 @@ public class AuthMePlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onSignChange(SignChangeEvent event) { Player player = event.getPlayer(); - if (ListenerService.shouldCancelEvent(player)) { + if (listenerService.shouldCancelEvent(player)) { event.setCancelled(true); } } + // TODO: check this, why do we need to update the quit loc? -sgdc3 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlayerRespawn(PlayerRespawnEvent event) { - if (!shouldCancelEvent(event)) { + if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { + return; + } + if (!listenerService.shouldCancelEvent(event)) { return; } - Player player = event.getPlayer(); String name = player.getName().toLowerCase(); - Location spawn = plugin.getSpawnLocation(player); - if (Settings.isSaveQuitLocationEnabled && dataSource.isAuthAvailable(name)) { + Location spawn = spawnLoader.getSpawnLocation(player); + if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && dataSource.isAuthAvailable(name)) { PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) @@ -529,15 +400,16 @@ public class AuthMePlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerShear(PlayerShearEntityEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerFish(PlayerFishEvent event) { - if (shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } + } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java index ee0e581c..871757f4 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener16.java @@ -5,14 +5,19 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerEditBookEvent; +import javax.inject.Inject; + /** * Listener of player events for events introduced in Minecraft 1.6. */ public class AuthMePlayerListener16 implements Listener { + @Inject + private ListenerService listenerService; + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerEditBook(PlayerEditBookEvent event) { - if (ListenerService.shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java index 560f6e8b..b6cbf2a7 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener18.java @@ -5,14 +5,19 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import javax.inject.Inject; + /** * Listener of player events for events introduced in Minecraft 1.8. */ public class AuthMePlayerListener18 implements Listener { + @Inject + private ListenerService listenerService; + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { - if (ListenerService.shouldCancelEvent(event)) { + if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener19.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener19.java deleted file mode 100644 index 5e6525bb..00000000 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener19.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.xephi.authme.listener; - -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.spigotmc.event.player.PlayerSpawnLocationEvent; - -import fr.xephi.authme.AuthMe; - -/** - * Listener of player events for events introduced in Minecraft 1.9. - */ -public class AuthMePlayerListener19 implements Listener { - - private final AuthMe plugin; - - public AuthMePlayerListener19(AuthMe plugin) { - this.plugin = plugin; - } - - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerSpawn(PlayerSpawnLocationEvent event) { - event.setSpawnLocation(plugin.getSpawnLocation(event.getPlayer())); - } - -} diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java index 96f5320a..b627ed6d 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java @@ -5,6 +5,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.ProtectionSettings; @@ -16,26 +17,26 @@ import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.event.server.ServerListPingEvent; +import javax.inject.Inject; + /** */ public class AuthMeServerListener implements Listener { - private final AuthMe plugin; - private final Messages messages; - private final NewSetting settings; - private final PluginHooks pluginHooks; - private final SpawnLoader spawnLoader; - private final ValidationService validationService; - - public AuthMeServerListener(AuthMe plugin, Messages messages, NewSetting settings, PluginHooks pluginHooks, - SpawnLoader spawnLoader, ValidationService validationService) { - this.plugin = plugin; - this.messages = messages; - this.settings = settings; - this.pluginHooks = pluginHooks; - this.spawnLoader = spawnLoader; - this.validationService = validationService; - } + @Inject + private AuthMe plugin; + @Inject + private Messages messages; + @Inject + private NewSetting settings; + @Inject + private PluginHooks pluginHooks; + @Inject + private SpawnLoader spawnLoader; + @Inject + private ValidationService validationService; + @Inject + private PermissionsManager permissionsManager; @EventHandler(priority = EventPriority.HIGHEST) public void onServerPing(ServerListPingEvent event) { @@ -54,10 +55,11 @@ public class AuthMeServerListener implements Listener { return; } - // Call the onPluginDisable method in the permissions manager - plugin.getPermissionsManager().onPluginDisable(event); - final String pluginName = event.getPlugin().getName(); + + // Call the onPluginDisable method in the permissions manager + permissionsManager.onPluginDisable(pluginName); + if ("Essentials".equalsIgnoreCase(pluginName)) { pluginHooks.unhookEssentials(); ConsoleLogger.info("Essentials has been disabled: unhooking"); @@ -87,10 +89,11 @@ public class AuthMeServerListener implements Listener { return; } - // Call the onPluginEnable method in the permissions manager - plugin.getPermissionsManager().onPluginEnable(event); - final String pluginName = event.getPlugin().getName(); + + // Call the onPluginEnable method in the permissions manager + permissionsManager.onPluginEnable(pluginName); + if ("Essentials".equalsIgnoreCase(pluginName)) { pluginHooks.tryHookToEssentials(); } else if ("Multiverse-Core".equalsIgnoreCase(pluginName)) { diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeTabCompletePacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeTabCompletePacketAdapter.java index d8266985..ae6c47bc 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeTabCompletePacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeTabCompletePacketAdapter.java @@ -11,8 +11,11 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; +import javax.inject.Inject; + public class AuthMeTabCompletePacketAdapter extends PacketAdapter { + @Inject public AuthMeTabCompletePacketAdapter(AuthMe plugin) { super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.TAB_COMPLETE); } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java index 6d4ea8ba..17b4a60a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java @@ -14,14 +14,13 @@ import com.comphenix.protocol.wrappers.EnumWrappers.PlayerInfoAction; import com.comphenix.protocol.wrappers.PlayerInfoData; import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedGameProfile; - import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.util.BukkitService; - import org.bukkit.entity.Player; +import javax.inject.Inject; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.logging.Level; @@ -30,6 +29,7 @@ public class AuthMeTablistPacketAdapter extends PacketAdapter { private final BukkitService bukkitService; + @Inject public AuthMeTablistPacketAdapter(AuthMe plugin, BukkitService bukkitService) { super(plugin, ListenerPriority.NORMAL, PacketType.Play.Server.PLAYER_INFO); this.bukkitService = bukkitService; diff --git a/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java b/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java new file mode 100644 index 00000000..31957cdb --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java @@ -0,0 +1,33 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.StringUtils; + +/** + * Exception thrown when a verification has failed. + */ +@SuppressWarnings("serial") +public class FailedVerificationException extends Exception { + + private final MessageKey reason; + private final String[] args; + + public FailedVerificationException(MessageKey reason, String... args) { + this.reason = reason; + this.args = args; + } + + public MessageKey getReason() { + return reason; + } + + public String[] getArgs() { + return args; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": reason=" + (reason == null ? "null" : reason) + + ";args=" + (args == null ? "null" : StringUtils.join(", ", args)); + } +} diff --git a/src/main/java/fr/xephi/authme/listener/ListenerService.java b/src/main/java/fr/xephi/authme/listener/ListenerService.java index 449dec67..c5de1bc3 100644 --- a/src/main/java/fr/xephi/authme/listener/ListenerService.java +++ b/src/main/java/fr/xephi/authme/listener/ListenerService.java @@ -1,26 +1,48 @@ package fr.xephi.authme.listener; -import fr.xephi.authme.util.Utils; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.hooks.PluginHooks; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.player.PlayerEvent; -/** - * Service class for the AuthMe listeners. - */ -final class ListenerService { +import javax.inject.Inject; +import java.util.HashSet; +import java.util.Set; - private ListenerService() { +/** + * Service class for the AuthMe listeners to determine whether an event should be canceled. + */ +class ListenerService implements SettingsDependent { + + private final DataSource dataSource; + private final PluginHooks pluginHooks; + private final PlayerCache playerCache; + + private boolean isRegistrationForced; + private Set unrestrictedNames; + + @Inject + ListenerService(NewSetting settings, DataSource dataSource, PluginHooks pluginHooks, PlayerCache playerCache) { + this.dataSource = dataSource; + this.pluginHooks = pluginHooks; + this.playerCache = playerCache; + loadSettings(settings); } /** - * Return whether an event should be canceled (for unauthenticated, non-NPC players). + * Returns whether an event should be canceled (for unauthenticated, non-NPC players). * - * @param event The event to process - * @return True if the event should be canceled, false otherwise + * @param event the event to process + * @return true if the event should be canceled, false otherwise */ - public static boolean shouldCancelEvent(EntityEvent event) { + public boolean shouldCancelEvent(EntityEvent event) { Entity entity = event.getEntity(); if (entity == null || !(entity instanceof Player)) { return false; @@ -31,24 +53,57 @@ final class ListenerService { } /** - * Return whether an event should be canceled (for unauthenticated, non-NPC players). + * Returns whether an event should be canceled (for unauthenticated, non-NPC players). * - * @param event The event to process - * @return True if the event should be canceled, false otherwise + * @param event the event to process + * @return true if the event should be canceled, false otherwise */ - public static boolean shouldCancelEvent(PlayerEvent event) { + public boolean shouldCancelEvent(PlayerEvent event) { Player player = event.getPlayer(); return shouldCancelEvent(player); } /** - * Return, based on the player associated with the event, whether or not the event should be canceled. + * Returns, based on the player associated with the event, whether or not the event should be canceled. * - * @param player The player to verify - * @return True if the associated event should be canceled, false otherwise + * @param player the player to verify + * @return true if the associated event should be canceled, false otherwise */ - public static boolean shouldCancelEvent(Player player) { - return player != null && !Utils.checkAuth(player) && !Utils.isNPC(player); + public boolean shouldCancelEvent(Player player) { + return player != null && !checkAuth(player.getName()) && !pluginHooks.isNpc(player); } + @Override + public void loadSettings(NewSetting settings) { + isRegistrationForced = settings.getProperty(RegistrationSettings.FORCE); + // Keep unrestricted names as Set for more efficient contains() + unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)); + } + + /** + * Checks whether the player is allowed to perform actions (i.e. whether he is logged in + * or if other settings permit playing). + * + * @param name the name of the player to verify + * @return true if the player may play, false otherwise + */ + private boolean checkAuth(String name) { + if (isUnrestricted(name) || playerCache.isAuthenticated(name)) { + return true; + } + if (!isRegistrationForced && !dataSource.isAuthAvailable(name)) { + return true; + } + return false; + } + + /** + * Checks if the name is unrestricted according to the configured settings. + * + * @param name the name to verify + * @return true if unrestricted, false otherwise + */ + private boolean isUnrestricted(String name) { + return unrestrictedNames.contains(name.toLowerCase()); + } } diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java new file mode 100644 index 00000000..297e74c6 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -0,0 +1,211 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.AntiBot; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.ProtectionSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.ValidationService; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerLoginEvent; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.Collection; +import java.util.regex.Pattern; + +/** + * Service for performing various verifications when a player joins. + */ +class OnJoinVerifier implements Reloadable { + + @Inject + private NewSetting settings; + @Inject + private DataSource dataSource; + @Inject + private Messages messages; + @Inject + private PermissionsManager permissionsManager; + @Inject + private AntiBot antiBot; + @Inject + private ValidationService validationService; + @Inject + private BukkitService bukkitService; + @Inject + private Server server; + + private Pattern nicknamePattern; + + OnJoinVerifier() { } + + + @PostConstruct + @Override + public void reload() { + String nickRegEx = settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS); + try { + nicknamePattern = Pattern.compile(nickRegEx); + } catch (Exception e) { + nicknamePattern = Pattern.compile(".*?"); + ConsoleLogger.showError("Nickname pattern is not a valid regular expression! " + + "Fallback to allowing all nicknames"); + } + } + + /** + * Checks if Antibot is enabled. + * + * @param playerName the name of the player (lowercase) + * @param isAuthAvailable whether or not the player is registered + */ + public void checkAntibot(String playerName, boolean isAuthAvailable) throws FailedVerificationException { + if (antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE && !isAuthAvailable) { + antiBot.antibotKicked.addIfAbsent(playerName); + throw new FailedVerificationException(MessageKey.KICK_ANTIBOT); + } + } + + /** + * Checks whether non-registered players should be kicked, and if so, whether the player should be kicked. + * + * @param isAuthAvailable whether or not the player is registered + */ + public void checkKickNonRegistered(boolean isAuthAvailable) throws FailedVerificationException { + if (!isAuthAvailable && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { + throw new FailedVerificationException(MessageKey.MUST_REGISTER_MESSAGE); + } + } + + /** + * Checks that the name adheres to the configured username restrictions. + * + * @param name the name to verify + */ + public void checkIsValidName(String name) throws FailedVerificationException { + if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) + || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { + throw new FailedVerificationException(MessageKey.INVALID_NAME_LENGTH); + } + if (!nicknamePattern.matcher(name).matches()) { + throw new FailedVerificationException(MessageKey.INVALID_NAME_CHARACTERS, nicknamePattern.pattern()); + } + } + + /** + * Handles the case of a full server and verifies if the user's connection should really be refused + * by adjusting the event object accordingly. Attempts to kick a non-VIP player to make room if the + * joining player is a VIP. + * + * @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 + */ + public boolean refusePlayerForFullServer(PlayerLoginEvent event) { + final Player player = event.getPlayer(); + if (event.getResult() != PlayerLoginEvent.Result.KICK_FULL) { + // Server is not full, no need to do anything + return false; + } else if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { + // Server is full and player is NOT VIP; set kick message and proceed with kick + event.setKickMessage(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + return true; + } + + // Server is full and player is VIP; attempt to kick a non-VIP player to make room + Collection onlinePlayers = bukkitService.getOnlinePlayers(); + if (onlinePlayers.size() < server.getMaxPlayers()) { + event.allow(); + return false; + } + Player nonVipPlayer = generateKickPlayer(onlinePlayers); + if (nonVipPlayer != null) { + nonVipPlayer.kickPlayer(messages.retrieveSingle(MessageKey.KICK_FOR_VIP)); + event.allow(); + return false; + } else { + ConsoleLogger.info("VIP player " + player.getName() + " tried to join, but the server was full"); + event.setKickMessage(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + return true; + } + } + + /** + * Checks that the casing in the username corresponds to the one in the database, if so configured. + * + * @param player the player to verify + * @param auth the auth object associated with the player + */ + public void checkNameCasing(Player player, PlayerAuth auth) throws FailedVerificationException { + if (auth != null && settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)) { + String realName = auth.getRealName(); // might be null or "Player" + String connectingName = player.getName(); + + if (StringUtils.isEmpty(realName) || "Player".equals(realName)) { + dataSource.updateRealName(connectingName.toLowerCase(), connectingName); + } else if (!realName.equals(connectingName)) { + throw new FailedVerificationException(MessageKey.INVALID_NAME_CASE, realName, connectingName); + } + } + } + + /** + * Checks that the player's country is admitted if he is not registered. + * + * @param isAuthAvailable whether or not the user is registered + * @param event the login event of the player + */ + public void checkPlayerCountry(boolean isAuthAvailable, + PlayerLoginEvent event) throws FailedVerificationException { + if (!isAuthAvailable && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { + String playerIp = event.getAddress().getHostAddress(); + if (!validationService.isCountryAdmitted(playerIp)) { + throw new FailedVerificationException(MessageKey.COUNTRY_BANNED_ERROR); + } + } + } + + /** + * Checks if a player with the same name (case-insensitive) is already playing and refuses the + * connection if so configured. + * + * @param name the player name to check + */ + public void checkSingleSession(String name) throws FailedVerificationException { + if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { + return; + } + + Player onlinePlayer = bukkitService.getPlayerExact(name); + if (onlinePlayer != null) { + throw new FailedVerificationException(MessageKey.USERNAME_ALREADY_ONLINE_ERROR); + } + } + + /** + * Selects a non-VIP player to kick when a VIP player joins the server when full. + * + * @param onlinePlayers list of online players + * @return the player to kick, or null if none applicable + */ + private Player generateKickPlayer(Collection onlinePlayers) { + for (Player player : onlinePlayers) { + if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { + return player; + } + } + return null; + } +} diff --git a/src/main/java/fr/xephi/authme/ImageGenerator.java b/src/main/java/fr/xephi/authme/mail/ImageGenerator.java similarity index 88% rename from src/main/java/fr/xephi/authme/ImageGenerator.java rename to src/main/java/fr/xephi/authme/mail/ImageGenerator.java index e529efab..77d98901 100644 --- a/src/main/java/fr/xephi/authme/ImageGenerator.java +++ b/src/main/java/fr/xephi/authme/mail/ImageGenerator.java @@ -1,6 +1,9 @@ -package fr.xephi.authme; +package fr.xephi.authme.mail; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.GradientPaint; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; /** diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index c80001c3..58b64d6b 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -2,7 +2,6 @@ package fr.xephi.authme.mail; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.ImageGenerator; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.EmailSettings; @@ -132,7 +131,7 @@ public class SendMailSSL { .replace("", newPass); } - private static void setPropertiesForPort(HtmlEmail email, int port, NewSetting settings) + private static void setPropertiesForPort(HtmlEmail email, int port, NewSetting settings) throws EmailException { switch (port) { case 587: @@ -153,7 +152,6 @@ public class SendMailSSL { } else { email.setStartTLSEnabled(true); email.setStartTLSRequired(true); - email.setTLS(true); } break; case 25: @@ -162,7 +160,7 @@ public class SendMailSSL { break; case 465: email.setSslSmtpPort(Integer.toString(port)); - email.setSSL(true); + email.setSSLOnConnect(true); break; default: email.setStartTLSEnabled(true); diff --git a/src/main/java/fr/xephi/authme/output/Log4JFilter.java b/src/main/java/fr/xephi/authme/output/Log4JFilter.java index 336af779..8bc52452 100644 --- a/src/main/java/fr/xephi/authme/output/Log4JFilter.java +++ b/src/main/java/fr/xephi/authme/output/Log4JFilter.java @@ -59,16 +59,16 @@ public class Log4JFilter implements Filter { @Override public Result filter(Logger arg0, Level arg1, Marker arg2, String message, Object... arg4) { - if (message == null) { - return Result.NEUTRAL; + if (message == null) { + return Result.NEUTRAL; } return validateMessage(message); } @Override public Result filter(Logger arg0, Level arg1, Marker arg2, Object message, Throwable arg4) { - if (message == null) { - return Result.NEUTRAL; + if (message == null) { + return Result.NEUTRAL; } return validateMessage(message.toString()); } diff --git a/src/main/java/fr/xephi/authme/output/MessageKey.java b/src/main/java/fr/xephi/authme/output/MessageKey.java index 3fd7c747..670366f5 100644 --- a/src/main/java/fr/xephi/authme/output/MessageKey.java +++ b/src/main/java/fr/xephi/authme/output/MessageKey.java @@ -5,6 +5,12 @@ package fr.xephi.authme.output; */ public enum MessageKey { + DENIED_COMMAND("denied_command"), + + SAME_IP_ONLINE("same_ip_online"), + + DENIED_CHAT("denied_chat"), + KICK_ANTIBOT("kick_antibot"), UNKNOWN_USER("unknown_user"), @@ -57,6 +63,8 @@ public enum MessageKey { PASSWORD_UNSAFE_ERROR("password_error_unsafe"), + PASSWORD_CHARACTERS_ERROR("password_error_chars", "REG_EX"), + SESSION_EXPIRED("invalid_session"), MUST_REGISTER_MESSAGE("reg_only"), @@ -129,7 +137,13 @@ public enum MessageKey { NOT_OWNER_ERROR("not_owner_error"), - INVALID_NAME_CASE("invalid_name_case", "%valid", "%invalid"); + INVALID_NAME_CASE("invalid_name_case", "%valid", "%invalid"), + + TEMPBAN_MAX_LOGINS("tempban_max_logins"), + + ACCOUNTS_OWNED_SELF("accounts_owned_self", "%count"), + + ACCOUNTS_OWNED_OTHER("accounts_owned_other", "%name", "%count"); private String key; private String[] tags; diff --git a/src/main/java/fr/xephi/authme/output/Messages.java b/src/main/java/fr/xephi/authme/output/Messages.java index 8ac42a7b..61bf3d7d 100644 --- a/src/main/java/fr/xephi/authme/output/Messages.java +++ b/src/main/java/fr/xephi/authme/output/Messages.java @@ -1,6 +1,8 @@ package fr.xephi.authme.output; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -14,7 +16,7 @@ import java.io.InputStreamReader; /** * Class for retrieving and sending translatable messages to players. */ -public class Messages { +public class Messages implements SettingsDependent { private FileConfiguration configuration; private String fileName; @@ -114,13 +116,9 @@ public class Messages { return message; } - /** - * Reset the messages manager to retrieve messages from the given file instead of the current one. - * - * @param messagesFile The new file to load messages from - */ - public void reload(File messagesFile) { - initializeFile(messagesFile); + @Override + public void loadSettings(NewSetting settings) { + initializeFile(settings.getMessagesFile()); } private void initializeFile(File messageFile) { diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java index 6dedf827..043ba6dc 100644 --- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java +++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java @@ -8,115 +8,121 @@ public enum AdminPermission implements PermissionNode { /** * Administrator command to register a new user. */ - REGISTER("authme.admin.register"), + REGISTER("authme.admin.register", DefaultPermission.OP_ONLY), /** * Administrator command to unregister an existing user. */ - UNREGISTER("authme.admin.unregister"), + UNREGISTER("authme.admin.unregister", DefaultPermission.OP_ONLY), /** * Administrator command to force-login an existing user. */ - FORCE_LOGIN("authme.admin.forcelogin"), + FORCE_LOGIN("authme.admin.forcelogin", DefaultPermission.OP_ONLY), /** * Administrator command to change the password of a user. */ - CHANGE_PASSWORD("authme.admin.changepassword"), + CHANGE_PASSWORD("authme.admin.changepassword", DefaultPermission.OP_ONLY), /** * Administrator command to see the last login date and time of a user. */ - LAST_LOGIN("authme.admin.lastlogin"), + LAST_LOGIN("authme.admin.lastlogin", DefaultPermission.OP_ONLY), /** * Administrator command to see all accounts associated with a user. */ - ACCOUNTS("authme.admin.accounts"), + ACCOUNTS("authme.admin.accounts", DefaultPermission.OP_ONLY), /** * Administrator command to get the email address of a user, if set. */ - GET_EMAIL("authme.admin.getemail"), + GET_EMAIL("authme.admin.getemail", DefaultPermission.OP_ONLY), /** * Administrator command to set or change the email address of a user. */ - CHANGE_EMAIL("authme.admin.changemail"), + CHANGE_EMAIL("authme.admin.changemail", DefaultPermission.OP_ONLY), /** * Administrator command to get the last known IP of a user. */ - GET_IP("authme.admin.getip"), + GET_IP("authme.admin.getip", DefaultPermission.OP_ONLY), /** * Administrator command to teleport to the AuthMe spawn. */ - SPAWN("authme.admin.spawn"), + SPAWN("authme.admin.spawn", DefaultPermission.OP_ONLY), /** * Administrator command to set the AuthMe spawn. */ - SET_SPAWN("authme.admin.setspawn"), + SET_SPAWN("authme.admin.setspawn", DefaultPermission.OP_ONLY), /** * Administrator command to teleport to the first AuthMe spawn. */ - FIRST_SPAWN("authme.admin.firstspawn"), + FIRST_SPAWN("authme.admin.firstspawn", DefaultPermission.OP_ONLY), /** * Administrator command to set the first AuthMe spawn. */ - SET_FIRST_SPAWN("authme.admin.setfirstspawn"), + SET_FIRST_SPAWN("authme.admin.setfirstspawn", DefaultPermission.OP_ONLY), /** * Administrator command to purge old user data. */ - PURGE("authme.admin.purge"), + PURGE("authme.admin.purge", DefaultPermission.OP_ONLY), /** * Administrator command to purge the last position of a user. */ - PURGE_LAST_POSITION("authme.admin.purgelastpos"), + PURGE_LAST_POSITION("authme.admin.purgelastpos", DefaultPermission.OP_ONLY), /** * Administrator command to purge all data associated with banned players. */ - PURGE_BANNED_PLAYERS("authme.admin.purgebannedplayers"), + PURGE_BANNED_PLAYERS("authme.admin.purgebannedplayers", DefaultPermission.OP_ONLY), /** * Administrator command to toggle the AntiBot protection status. */ - SWITCH_ANTIBOT("authme.admin.switchantibot"), + SWITCH_ANTIBOT("authme.admin.switchantibot", DefaultPermission.OP_ONLY), /** * Administrator command to convert old or other data to AuthMe data. */ - CONVERTER("authme.admin.converter"), + CONVERTER("authme.admin.converter", DefaultPermission.OP_ONLY), /** * Administrator command to reload the plugin configuration. */ - RELOAD("authme.admin.reload"), + RELOAD("authme.admin.reload", DefaultPermission.OP_ONLY), /** * Permission to see the other accounts of the players that log in. */ - SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts"); + SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts", DefaultPermission.OP_ONLY); /** * The permission node. */ private String node; + /** + * The default permission level + */ + private DefaultPermission defaultPermission; + /** * Constructor. * * @param node Permission node. */ - AdminPermission(String node) { + AdminPermission(String node, DefaultPermission defaultPermission) { this.node = node; + this.defaultPermission = defaultPermission; } @Override @@ -124,4 +130,8 @@ public enum AdminPermission implements PermissionNode { return node; } + @Override + public DefaultPermission getDefaultPermission() { + return defaultPermission; + } } diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java new file mode 100644 index 00000000..c7557fc2 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -0,0 +1,84 @@ +package fr.xephi.authme.permission; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.PluginSettings; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.Arrays; + +/** + * Changes the permission group according to the auth status of the player and the configuration. + */ +public class AuthGroupHandler { + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private NewSetting settings; + + @Inject + private LimboCache limboCache; + + AuthGroupHandler() { } + + /** + * Set the group of a player, by its AuthMe group type. + * + * @param player The player. + * @param group The group type. + * + * @return True if succeeded, false otherwise. False is also returned if groups aren't supported + * with the current permissions system. + */ + public boolean setGroup(Player player, AuthGroupType group) { + // Check whether the permissions check is enabled + if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) { + return false; + } + + // Make sure group support is available + if (!permissionsManager.hasGroupSupport()) { + ConsoleLogger.showError("The current permissions system doesn't have group support, unable to set group!"); + return false; + } + + switch (group) { + case UNREGISTERED: + // Remove the other group type groups, set the current group + permissionsManager.removeGroups(player, Arrays.asList(Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); + return permissionsManager.addGroup(player, Settings.unRegisteredGroup); + + case REGISTERED: + // Remove the other group type groups, set the current group + permissionsManager.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getUnloggedinGroup)); + return permissionsManager.addGroup(player, Settings.getRegisteredGroup); + + case NOT_LOGGED_IN: + // Remove the other group type groups, set the current group + permissionsManager.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup)); + return permissionsManager.addGroup(player, Settings.getUnloggedinGroup); + + case LOGGED_IN: + // Get the limbo player data + LimboPlayer limbo = limboCache.getLimboPlayer(player.getName().toLowerCase()); + if (limbo == null) + return false; + + // Get the players group + String realGroup = limbo.getGroup(); + + // Remove the other group types groups, set the real group + permissionsManager.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); + return permissionsManager.addGroup(player, realGroup); + default: + return false; + } + } + +} diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupType.java b/src/main/java/fr/xephi/authme/permission/AuthGroupType.java new file mode 100644 index 00000000..9ab2a370 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupType.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.permission; + +/** + * Represents the group type based on the user's auth status. + */ +public enum AuthGroupType { + + /** Player does not have an account. */ + UNREGISTERED, + + /** Registered? */ + REGISTERED, // TODO #761: Remove this or the NOT_LOGGED_IN one + + /** Player is registered and not logged in. */ + NOT_LOGGED_IN, + + /** Player is logged in. */ + LOGGED_IN + +} diff --git a/src/main/java/fr/xephi/authme/permission/DefaultPermission.java b/src/main/java/fr/xephi/authme/permission/DefaultPermission.java index 9ad8aff1..3acbfa8c 100644 --- a/src/main/java/fr/xephi/authme/permission/DefaultPermission.java +++ b/src/main/java/fr/xephi/authme/permission/DefaultPermission.java @@ -1,18 +1,35 @@ package fr.xephi.authme.permission; +import org.bukkit.command.CommandSender; + /** - * The default permission for a command if there is no support for permission nodes. + * The default permission to fall back to if there is no support for permission nodes. */ public enum DefaultPermission { - /** No one can execute the command. */ - NOT_ALLOWED("No permission"), + /** No one has permission. */ + NOT_ALLOWED("No permission") { + @Override + public boolean evaluate(CommandSender sender) { + return false; + } + }, - /** Only players with the OP status may execute the command. */ - OP_ONLY("OP's only"), + /** Only players with OP status have permission. */ + OP_ONLY("OP's only") { + @Override + public boolean evaluate(CommandSender sender) { + return sender.isOp(); + } + }, - /** The command can be executed by anyone. */ - ALLOWED("Everyone allowed"); + /** Everyone is granted permission. */ + ALLOWED("Everyone allowed") { + @Override + public boolean evaluate(CommandSender sender) { + return true; + } + }; /** Textual representation of the default permission. */ private final String title; @@ -26,9 +43,17 @@ public enum DefaultPermission { } /** - * Return the textual representation. - * - * @return The textual representation + * Evaluates whether permission is granted to the sender or not. + * + * @param sender the sender to process + * @return true if the sender has permission, false otherwise + */ + public abstract boolean evaluate(CommandSender sender); + + /** + * Return the textual representation. + * + * @return the textual representation */ public String getTitle() { return title; diff --git a/src/main/java/fr/xephi/authme/permission/PermissionNode.java b/src/main/java/fr/xephi/authme/permission/PermissionNode.java index 04f9c80c..189f97b5 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionNode.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionNode.java @@ -12,4 +12,10 @@ public interface PermissionNode { */ String getNode(); + /** + * Return the default permission for this node, e.g. "OP_ONLY" + * + * @return The default level of permission + */ + DefaultPermission getDefaultPermission(); } diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index ee27f82c..e0065fa4 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -1,30 +1,30 @@ package fr.xephi.authme.permission; -import de.bananaco.bpermissions.api.ApiLayer; -import de.bananaco.bpermissions.api.CalculableType; -import fr.xephi.authme.command.CommandDescription; -import fr.xephi.authme.util.CollectionUtils; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.permission.handlers.BPermissionsHandler; +import fr.xephi.authme.permission.handlers.GroupManagerHandler; +import fr.xephi.authme.permission.handlers.PermissionHandler; +import fr.xephi.authme.permission.handlers.PermissionsBukkitHandler; +import fr.xephi.authme.permission.handlers.PermissionsExHandler; +import fr.xephi.authme.permission.handlers.VaultHandler; +import fr.xephi.authme.permission.handlers.ZPermissionsHandler; +import fr.xephi.authme.util.StringUtils; import net.milkbowl.vault.permission.Permission; import org.anjocaido.groupmanager.GroupManager; -import org.anjocaido.groupmanager.permissions.AnjoPermissionsHandler; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.RegisteredServiceProvider; import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService; -import ru.tehkode.permissions.PermissionUser; import ru.tehkode.permissions.bukkit.PermissionsEx; +import javax.annotation.PostConstruct; +import javax.inject.Inject; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.logging.Logger; /** *

@@ -38,43 +38,27 @@ import java.util.logging.Logger; * @author Tim Visée, http://timvisee.com * @version 0.3 */ -public class PermissionsManager implements PermissionsService { +public class PermissionsManager { + + private final Server server; + private final PluginManager pluginManager; /** - * Vault instance. + * The permission handler that is currently in use. + * Null if no permission system is hooked. */ - public Permission vaultPerms = null; - /** - * Server instance. - */ - private final Server server; - /** - * Logger instance. - */ - private Logger log; - /** - * Type of permissions system that is currently used. - * Null if no permissions system is hooked and/or used. - */ - private PermissionsSystemType permsType = null; - /** - * Essentials group manager instance. - */ - private GroupManager groupManagerPerms; - /** - * zPermissions service instance. - */ - private ZPermissionsService zPermissionsService; + private PermissionHandler handler = null; /** * Constructor. * * @param server Server instance - * @param log Logger + * @param pluginManager Bukkit plugin manager */ - public PermissionsManager(Server server, Logger log) { + @Inject + public PermissionsManager(Server server, PluginManager pluginManager) { this.server = server; - this.log = log; + this.pluginManager = pluginManager; } /** @@ -83,114 +67,105 @@ public class PermissionsManager implements PermissionsService { * @return False if there isn't any permissions system used. */ public boolean isEnabled() { - return permsType != null; - } - - /** - * Return the permissions system where the permissions manager is currently hooked into. - * - * @return The name of the permissions system used. - */ - public PermissionsSystemType getSystem() { - return permsType; + return handler != null; } /** * Setup and hook into the permissions systems. - * - * @return The detected permissions system. */ - public PermissionsSystemType setup() { + @PostConstruct + public void setup() { // Force-unhook from current hooked permissions systems unhook(); - // Define the plugin manager - final PluginManager pluginManager = this.server.getPluginManager(); - - // Reset used permissions system type flag - permsType = null; - // Loop through all the available permissions system types - for(PermissionsSystemType type : PermissionsSystemType.values()) { + for (PermissionsSystemType type : PermissionsSystemType.values()) { // Try to find and hook the current plugin if available, print an error if failed try { // Try to find the plugin for the current permissions system Plugin plugin = pluginManager.getPlugin(type.getPluginName()); // Make sure a plugin with this name was found - if(plugin == null) + if (plugin == null) continue; // Make sure the plugin is enabled before hooking - if(!plugin.isEnabled()) { - this.log.info("Not hooking into " + type.getName() + " because it's disabled!"); + if (!plugin.isEnabled()) { + ConsoleLogger.info("Not hooking into " + type.getName() + " because it's disabled!"); continue; } // Use the proper method to hook this plugin - switch(type) { + switch (type) { case PERMISSIONS_EX: // Get the permissions manager for PermissionsEx and make sure it isn't null - if(PermissionsEx.getPermissionManager() == null) { - this.log.info("Failed to hook into " + type.getName() + "!"); + if (PermissionsEx.getPermissionManager() == null) { + ConsoleLogger.info("Failed to hook into " + type.getName() + "!"); continue; } + handler = new PermissionsExHandler(PermissionsEx.getPermissionManager()); break; case ESSENTIALS_GROUP_MANAGER: // Set the plugin instance - groupManagerPerms = (GroupManager) plugin; + handler = new GroupManagerHandler((GroupManager) plugin); break; case Z_PERMISSIONS: // Set the zPermissions service and make sure it's valid - zPermissionsService = Bukkit.getServicesManager().load(ZPermissionsService.class); - if(zPermissionsService == null) { - this.log.info("Failed to hook into " + type.getName() + "!"); + ZPermissionsService zPermissionsService = Bukkit.getServicesManager().load(ZPermissionsService.class); + if (zPermissionsService == null) { + ConsoleLogger.info("Failed to hook into " + type.getName() + "!"); continue; } + handler = new ZPermissionsHandler(zPermissionsService); break; case VAULT: // Get the permissions provider service RegisteredServiceProvider permissionProvider = this.server.getServicesManager().getRegistration(Permission.class); if (permissionProvider == null) { - this.log.info("Failed to hook into " + type.getName() + "!"); + ConsoleLogger.info("Failed to hook into " + type.getName() + "!"); continue; } // Get the Vault provider and make sure it's valid - vaultPerms = permissionProvider.getProvider(); - if(vaultPerms == null) { - this.log.info("Not using " + type.getName() + " because it's disabled!"); + Permission vaultPerms = permissionProvider.getProvider(); + if (vaultPerms == null) { + ConsoleLogger.info("Not using " + type.getName() + " because it's disabled!"); continue; } + handler = new VaultHandler(vaultPerms); + break; + + case B_PERMISSIONS: + handler = new BPermissionsHandler(); + break; + + case PERMISSIONS_BUKKIT: + handler = new PermissionsBukkitHandler(); break; default: } - // Set the hooked permissions system type - this.permsType = type; - // Show a success message - this.log.info("Hooked into " + type.getName() + "!"); + ConsoleLogger.info("Hooked into " + type.getName() + "!"); // Return the used permissions system type - return type; + return; } catch (Exception ex) { // An error occurred, show a warning message - this.log.info("Error while hooking into " + type.getName() + "!"); + ConsoleLogger.logException("Error while hooking into " + type.getName(), ex); } } // No recognized permissions system found, show a message and return - this.log.info("No supported permissions system found! Permissions are disabled!"); - return null; + ConsoleLogger.info("No supported permissions system found! Permissions are disabled!"); } /** @@ -198,41 +173,32 @@ public class PermissionsManager implements PermissionsService { */ public void unhook() { // Reset the current used permissions system - this.permsType = null; + this.handler = null; // Print a status message to the console - this.log.info("Unhooked from Permissions!"); + ConsoleLogger.info("Unhooked from Permissions!"); } /** * Reload the permissions manager, and re-hook all permission plugins. - * - * @return True on success, false on failure. */ - public boolean reload() { + public void reload() { // Unhook all permission plugins unhook(); // Set up the permissions manager again, return the result setup(); - return true; } /** * Method called when a plugin is being enabled. * - * @param event Event instance. + * @param pluginName The name of the plugin being enabled. */ - public void onPluginEnable(PluginEnableEvent event) { - // Get the plugin and it's name - Plugin plugin = event.getPlugin(); - String pluginName = plugin.getName(); - + public void onPluginEnable(String pluginName) { // Check if any known permissions system is enabling - if (pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || - pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || - pluginName.equals("zPermissions") || pluginName.equals("Vault")) { - this.log.info(pluginName + " plugin enabled, dynamically updating permissions hooks!"); + if (PermissionsSystemType.isPermissionSystem(pluginName)) { + ConsoleLogger.info(pluginName + " plugin enabled, dynamically updating permissions hooks!"); setup(); } } @@ -240,18 +206,12 @@ public class PermissionsManager implements PermissionsService { /** * Method called when a plugin is being disabled. * - * @param event Event instance. + * @param pluginName The name of the plugin being disabled. */ - public void onPluginDisable(PluginDisableEvent event) { - // Get the plugin instance and name - Plugin plugin = event.getPlugin(); - String pluginName = plugin.getName(); - - // Is the WorldGuard plugin disabled - if (pluginName.equals("PermissionsEx") || pluginName.equals("PermissionsBukkit") || - pluginName.equals("bPermissions") || pluginName.equals("GroupManager") || - pluginName.equals("zPermissions") || pluginName.equals("Vault")) { - this.log.info(pluginName + " plugin disabled, updating hooks!"); + public void onPluginDisable(String pluginName) { + // Check if any known permission system is being disabled + if (PermissionsSystemType.isPermissionSystem(pluginName)) { + ConsoleLogger.info(pluginName + " plugin disabled, updating hooks!"); setup(); } } @@ -266,104 +226,18 @@ public class PermissionsManager implements PermissionsService { * @return True if the sender has the permission, false otherwise. */ public boolean hasPermission(CommandSender sender, PermissionNode permissionNode) { - return hasPermission(sender, permissionNode, sender.isOp()); - } - - public boolean hasPermission(CommandSender sender, PermissionNode permissionNode, boolean def) { - if (!(sender instanceof Player)) { - return def; - } - - Player player = (Player) sender; - return hasPermission(player, permissionNode.getNode(), def); - } - - public boolean hasPermission(Player player, Iterable nodes, boolean def) { - for (PermissionNode node : nodes) { - if (!hasPermission(player, node, def)) { - return false; - } - } - return true; - } - - public boolean hasPermission(CommandSender sender, CommandDescription command) { - if (command.getCommandPermissions() == null - || CollectionUtils.isEmpty(command.getCommandPermissions().getPermissionNodes())) { + // Check if the permission node is null + if (permissionNode == null) { return true; } - DefaultPermission defaultPermission = command.getCommandPermissions().getDefaultPermission(); - boolean def = evaluateDefaultPermission(defaultPermission, sender); - return (sender instanceof Player) - ? hasPermission((Player) sender, command.getCommandPermissions().getPermissionNodes(), def) - : def; - } - - public static boolean evaluateDefaultPermission(DefaultPermission defaultPermission, CommandSender sender) { - switch (defaultPermission) { - case ALLOWED: - return true; - - case OP_ONLY: - return sender.isOp(); - - case NOT_ALLOWED: - default: - return false; + // Return if the player is an Op if sender is console or no permission system in use + if (!(sender instanceof Player) || !isEnabled()) { + return permissionNode.getDefaultPermission().evaluate(sender); } - } - /** - * Check if a player has permission. - * - * @param player The player. - * @param permsNode The permission node. - * @param def Default returned if no permissions system is used. - * - * @return True if the player has permission. - */ - private boolean hasPermission(Player player, String permsNode, boolean def) { - // If no permissions system is used, return the default value - if (!isEnabled()) - return def; - - switch (this.permsType) { - case PERMISSIONS_EX: - // Permissions Ex - PermissionUser user = PermissionsEx.getUser(player); - return user.has(permsNode); - - case PERMISSIONS_BUKKIT: - // Permissions Bukkit - return player.hasPermission(permsNode); - - case B_PERMISSIONS: - // bPermissions - return ApiLayer.hasPermission(player.getWorld().getName(), CalculableType.USER, player.getName(), permsNode); - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - return handler != null && handler.has(player, permsNode); - - case Z_PERMISSIONS: - // zPermissions - @SuppressWarnings("deprecation") - Map perms = zPermissionsService.getPlayerPermissions(player.getWorld().getName(), null, player.getName()); - if (perms.containsKey(permsNode)) - return perms.get(permsNode); - else - return def; - - case VAULT: - // Vault - return vaultPerms.has(player, permsNode); - - default: - // Not hooked into any permissions system, return default - return def; - } + Player player = (Player) sender; + return handler.hasPermission(player, permissionNode); } /** @@ -377,22 +251,7 @@ public class PermissionsManager implements PermissionsService { if (!isEnabled()) return false; - switch (this.permsType) { - case PERMISSIONS_EX: - case PERMISSIONS_BUKKIT: - case B_PERMISSIONS: - case ESSENTIALS_GROUP_MANAGER: - case Z_PERMISSIONS: - return true; - - case VAULT: - // Vault - return vaultPerms.hasGroupSupport(); - - default: - // Not hooked into any permissions system, return false - return false; - } + return handler.hasGroupSupport(); } /** @@ -402,46 +261,12 @@ public class PermissionsManager implements PermissionsService { * * @return Permission groups, or an empty list if this feature is not supported. */ - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) public List getGroups(Player player) { // If no permissions system is used, return an empty list if (!isEnabled()) return new ArrayList<>(); - switch (this.permsType) { - case PERMISSIONS_EX: - // Permissions Ex - PermissionUser user = PermissionsEx.getUser(player); - return user.getParentIdentifiers(null); - - case PERMISSIONS_BUKKIT: - // Permissions Bukkit - // FIXME: Add support for this! - return new ArrayList<>(); - - case B_PERMISSIONS: - // bPermissions - return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName())); - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - if (handler == null) - return new ArrayList<>(); - return Arrays.asList(handler.getGroups(player.getName())); - - case Z_PERMISSIONS: - //zPermissions - return new ArrayList(zPermissionsService.getPlayerGroups(player.getName())); - - case VAULT: - // Vault - return Arrays.asList(vaultPerms.getPlayerGroups(player)); - - default: - // Not hooked into any permissions system, return an empty list - return new ArrayList<>(); - } + return handler.getGroups(player); } /** @@ -451,45 +276,12 @@ public class PermissionsManager implements PermissionsService { * * @return The name of the primary permission group. Or null. */ - @SuppressWarnings("deprecation") public String getPrimaryGroup(Player player) { // If no permissions system is used, return an empty list if (!isEnabled()) return null; - switch (this.permsType) { - case PERMISSIONS_EX: - case PERMISSIONS_BUKKIT: - case B_PERMISSIONS: - // Get the groups of the player - List groups = getGroups(player); - - // Make sure there is any group available, or return null - if (groups.size() == 0) - return null; - - // Return the first group - return groups.get(0); - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - if (handler == null) - return null; - return handler.getGroup(player.getName()); - - case Z_PERMISSIONS: - //zPermissions - return zPermissionsService.getPlayerPrimaryGroup(player.getName()); - - case VAULT: - // Vault - return vaultPerms.getPrimaryGroup(player); - - default: - // Not hooked into any permissions system, return null - return null; - } + return handler.getPrimaryGroup(player); } /** @@ -506,40 +298,7 @@ public class PermissionsManager implements PermissionsService { if (!isEnabled()) return false; - switch (this.permsType) { - case PERMISSIONS_EX: - // Permissions Ex - PermissionUser user = PermissionsEx.getUser(player); - return user.inGroup(groupName); - - case PERMISSIONS_BUKKIT: - case Z_PERMISSIONS: - // Get the current list of groups - List groupNames = getGroups(player); - - // Check whether the list contains the group name, return the result - for (String entry : groupNames) - if (entry.equals(groupName)) - return true; - return false; - - case B_PERMISSIONS: - // bPermissions - return ApiLayer.hasGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - final AnjoPermissionsHandler handler = groupManagerPerms.getWorldsHolder().getWorldPermissions(player); - return handler != null && handler.inGroup(player.getName(), groupName); - - case VAULT: - // Vault - return vaultPerms.playerInGroup(player, groupName); - - default: - // Not hooked into any permissions system, return an empty list - return false; - } + return handler.isInGroup(player, groupName); } /** @@ -552,47 +311,16 @@ public class PermissionsManager implements PermissionsService { * False is also returned if this feature isn't supported for the current permissions system. */ public boolean addGroup(Player player, String groupName) { - // If no permissions system is used, return false - if (!isEnabled()) + if (StringUtils.isEmpty(groupName)) { return false; - - // Set the group the proper way - switch (this.permsType) { - case PERMISSIONS_EX: - // Permissions Ex - PermissionUser user = PermissionsEx.getUser(player); - user.addGroup(groupName); - return true; - - case PERMISSIONS_BUKKIT: - // Permissions Bukkit - // Add the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + groupName); - - case B_PERMISSIONS: - // bPermissions - ApiLayer.addGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); - return true; - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - // Add the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuaddsub " + player.getName() + " " + groupName); - - case Z_PERMISSIONS: - // zPermissions - // Add the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + groupName); - - case VAULT: - // Vault - vaultPerms.playerAddGroup(player, groupName); - return true; - - default: - // Not hooked into any permissions system, return false - return false; } + + // If no permissions system is used, return false + if (!isEnabled()) { + return false; + } + + return handler.addToGroup(player, groupName); } /** @@ -633,43 +361,7 @@ public class PermissionsManager implements PermissionsService { if (!isEnabled()) return false; - // Set the group the proper way - switch (this.permsType) { - case PERMISSIONS_EX: - // Permissions Ex - PermissionUser user = PermissionsEx.getUser(player); - user.removeGroup(groupName); - return true; - - case PERMISSIONS_BUKKIT: - // Permissions Bukkit - // Remove the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + groupName); - - case B_PERMISSIONS: - // bPermissions - ApiLayer.removeGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); - return true; - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - // Remove the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manudelsub " + player.getName() + " " + groupName); - - case Z_PERMISSIONS: - // zPermissions - // Remove the group to the user using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + groupName); - - case VAULT: - // Vault - vaultPerms.playerRemoveGroup(player, groupName); - return true; - - default: - // Not hooked into any permissions system, return false - return false; - } + return handler.removeFromGroup(player, groupName); } /** @@ -711,50 +403,7 @@ public class PermissionsManager implements PermissionsService { if (!isEnabled()) return false; - // Create a list of group names - List groupNames = new ArrayList<>(); - groupNames.add(groupName); - - // Set the group the proper way - switch (this.permsType) { - case PERMISSIONS_EX: - // Permissions Ex - PermissionUser user = PermissionsEx.getUser(player); - user.setParentsIdentifier(groupNames); - return true; - - case PERMISSIONS_BUKKIT: - // Permissions Bukkit - // Set the user's group using a command - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player setgroup " + player.getName() + " " + groupName); - - case B_PERMISSIONS: - // bPermissions - ApiLayer.setGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), groupName); - return true; - - case ESSENTIALS_GROUP_MANAGER: - // Essentials Group Manager - // Clear the list of groups, add the player to the specified group afterwards using a command - removeAllGroups(player); - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuadd " + player.getName() + " " + groupName); - - case Z_PERMISSIONS: - //zPermissions - // Set the players group through the plugin commands - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " setgroup " + groupName); - - case VAULT: - // Vault - // Remove all current groups, add the player to the specified group afterwards - removeAllGroups(player); - vaultPerms.playerAddGroup(player, groupName); - return true; - - default: - // Not hooked into any permissions system, return false - return false; - } + return handler.setGroup(player, groupName); } /** @@ -794,7 +443,7 @@ public class PermissionsManager implements PermissionsService { /** * Remove all groups of the specified player, if supported. * Systems like Essentials GroupManager don't allow all groups to be removed from a player, thus the user will stay - * in it's primary group. All the subgroups are removed just fine. + * in its primary group. All the subgroups are removed just fine. * * @param player The player to remove all groups from. * diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsService.java b/src/main/java/fr/xephi/authme/permission/PermissionsService.java deleted file mode 100644 index 9de4f68e..00000000 --- a/src/main/java/fr/xephi/authme/permission/PermissionsService.java +++ /dev/null @@ -1,39 +0,0 @@ -package fr.xephi.authme.permission; - -import fr.xephi.authme.command.CommandDescription; -import org.bukkit.command.CommandSender; - -/** - * Interface for dealing with permissions. - */ -public interface PermissionsService { - - /** - * Check if the player has the given permission. - * - * @param sender The command sender - * @param permission The permission node to check - * @param def Default returned if no permissions system is used - * - * @return True if the player has permission - */ - boolean hasPermission(CommandSender sender, PermissionNode permission, boolean def); - - /** - * Check if the player has the permissions for the given command. - * - * @param sender The command sender - * @param command The command whose permissions should be checked - * - * @return True if the player may execute the command - */ - boolean hasPermission(CommandSender sender, CommandDescription command); - - /** - * Return the permission system the service is working with. - * - * @return The permission system AuthMe is hooked into - */ - PermissionsSystemType getSystem(); - -} diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsSystemType.java b/src/main/java/fr/xephi/authme/permission/PermissionsSystemType.java index 2167fde6..88b09d19 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsSystemType.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsSystemType.java @@ -83,4 +83,19 @@ public enum PermissionsSystemType { public String toString() { return getName(); } + + /** + * Check if a given plugin is a permissions system. + * + * @param name The name of the plugin to check. + * @return If the plugin is a valid permissions system. + */ + public static boolean isPermissionSystem(String name) { + for (PermissionsSystemType permissionsSystemType : values()) { + if (permissionsSystemType.pluginName.equals(name)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/fr/xephi/authme/permission/PlayerPermission.java b/src/main/java/fr/xephi/authme/permission/PlayerPermission.java index 210cbce9..e2932e93 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerPermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerPermission.java @@ -8,70 +8,76 @@ public enum PlayerPermission implements PermissionNode { /** * Command permission to login. */ - LOGIN("authme.player.login"), + LOGIN("authme.player.login", DefaultPermission.ALLOWED), /** * Command permission to logout. */ - LOGOUT("authme.player.logout"), + LOGOUT("authme.player.logout", DefaultPermission.ALLOWED), /** * Command permission to register. */ - REGISTER("authme.player.register"), + REGISTER("authme.player.register", DefaultPermission.ALLOWED), /** * Command permission to unregister. */ - UNREGISTER("authme.player.unregister"), + UNREGISTER("authme.player.unregister", DefaultPermission.ALLOWED), /** * Command permission to change the password. */ - CHANGE_PASSWORD("authme.player.changepassword"), + CHANGE_PASSWORD("authme.player.changepassword", DefaultPermission.ALLOWED), /** * Command permission to add an email address. */ - ADD_EMAIL("authme.player.email.add"), + ADD_EMAIL("authme.player.email.add", DefaultPermission.ALLOWED), /** * Command permission to change the email address. */ - CHANGE_EMAIL("authme.player.email.change"), + CHANGE_EMAIL("authme.player.email.change", DefaultPermission.ALLOWED), /** * Command permission to recover an account using it's email address. */ - RECOVER_EMAIL("authme.player.email.recover"), + RECOVER_EMAIL("authme.player.email.recover", DefaultPermission.ALLOWED), /** * Command permission to use captcha. */ - CAPTCHA("authme.player.captcha"), + CAPTCHA("authme.player.captcha", DefaultPermission.ALLOWED), /** * Permission for users a login can be forced to. */ - CAN_LOGIN_BE_FORCED("authme.player.canbeforced"), + CAN_LOGIN_BE_FORCED("authme.player.canbeforced", DefaultPermission.ALLOWED), /** * Permission to use to see own other accounts. */ - SEE_OWN_ACCOUNTS("authme.player.seeownaccounts"); + SEE_OWN_ACCOUNTS("authme.player.seeownaccounts", DefaultPermission.ALLOWED); /** * The permission node. */ private String node; + /** + * The default permission level + */ + private DefaultPermission defaultPermission; + /** * Constructor. * * @param node Permission node. */ - PlayerPermission(String node) { + PlayerPermission(String node, DefaultPermission defaultPermission) { this.node = node; + this.defaultPermission = defaultPermission; } @Override @@ -79,4 +85,9 @@ public enum PlayerPermission implements PermissionNode { return node; } + @Override + public DefaultPermission getDefaultPermission() { + return defaultPermission; + } + } diff --git a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java index b91a1d4b..fd023935 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java @@ -9,39 +9,50 @@ public enum PlayerStatePermission implements PermissionNode { /** * Permission node to bypass AntiBot protection. */ - BYPASS_ANTIBOT("authme.bypassantibot"), + BYPASS_ANTIBOT("authme.bypassantibot", DefaultPermission.OP_ONLY), /** * Permission for users to bypass force-survival mode. */ - BYPASS_FORCE_SURVIVAL("authme.bypassforcesurvival"), + BYPASS_FORCE_SURVIVAL("authme.bypassforcesurvival", DefaultPermission.OP_ONLY), /** * Permission node to identify VIP users. */ - IS_VIP("authme.vip"), + IS_VIP("authme.vip", DefaultPermission.OP_ONLY), /** * Permission to be able to register multiple accounts. */ - ALLOW_MULTIPLE_ACCOUNTS("authme.allowmultipleaccounts"); + ALLOW_MULTIPLE_ACCOUNTS("authme.allowmultipleaccounts", DefaultPermission.OP_ONLY); /** * The permission node. */ private String node; + /** + * The default permission level + */ + private DefaultPermission defaultPermission; + /** * Constructor. * * @param node Permission node. */ - PlayerStatePermission(String node) { + PlayerStatePermission(String node, DefaultPermission defaultPermission) { this.node = node; + this.defaultPermission = defaultPermission; } @Override public String getNode() { return node; } + + @Override + public DefaultPermission getDefaultPermission() { + return defaultPermission; + } } diff --git a/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java new file mode 100644 index 00000000..45592323 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java @@ -0,0 +1,69 @@ +package fr.xephi.authme.permission.handlers; + +import de.bananaco.bpermissions.api.ApiLayer; +import de.bananaco.bpermissions.api.CalculableType; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.List; + +public class BPermissionsHandler implements PermissionHandler { + + @Override + public boolean addToGroup(Player player, String group) { + ApiLayer.addGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), group); + return true; + } + + @Override + public boolean hasGroupSupport() { + return true; + } + + @Override + public boolean hasPermission(Player player, PermissionNode node) { + return ApiLayer.hasPermission(player.getWorld().getName(), CalculableType.USER, player.getName(), node.getNode()); + } + + @Override + public boolean isInGroup(Player player, String group) { + return ApiLayer.hasGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), group); + } + + @Override + public boolean removeFromGroup(Player player, String group) { + ApiLayer.removeGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), group); + return true; + } + + @Override + public boolean setGroup(Player player, String group) { + ApiLayer.setGroup(player.getWorld().getName(), CalculableType.USER, player.getName(), group); + return true; + } + + @Override + public List getGroups(Player player) { + return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName())); + } + + @Override + public String getPrimaryGroup(Player player) { + // Get the groups of the player + List groups = getGroups(player); + + // Make sure there is any group available, or return null + if (groups.size() == 0) + return null; + + // Return the first group + return groups.get(0); + } + + @Override + public PermissionsSystemType getPermissionSystem() { + return PermissionsSystemType.B_PERMISSIONS; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/handlers/GroupManagerHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/GroupManagerHandler.java new file mode 100644 index 00000000..fe0b0236 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/GroupManagerHandler.java @@ -0,0 +1,79 @@ +package fr.xephi.authme.permission.handlers; + +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import org.anjocaido.groupmanager.GroupManager; +import org.anjocaido.groupmanager.permissions.AnjoPermissionsHandler; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GroupManagerHandler implements PermissionHandler { + + private GroupManager groupManager; + + public GroupManagerHandler(GroupManager groupManager) { + this.groupManager = groupManager; + } + + @Override + public boolean addToGroup(Player player, String group) { + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuaddsub " + player.getName() + " " + group); + } + + @Override + public boolean hasGroupSupport() { + return true; + } + + @Override + public boolean hasPermission(Player player, PermissionNode node) { + final AnjoPermissionsHandler handler = groupManager.getWorldsHolder().getWorldPermissions(player); + return handler != null && handler.has(player, node.getNode()); + } + + @Override + public boolean isInGroup(Player player, String group) { + final AnjoPermissionsHandler handler = groupManager.getWorldsHolder().getWorldPermissions(player); + return handler != null && handler.inGroup(player.getName(), group); + } + + @Override + public boolean removeFromGroup(Player player, String group) { + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manudelsub " + player.getName() + " " + group); + } + + @Override + public boolean setGroup(Player player, String group) { + final AnjoPermissionsHandler handler = groupManager.getWorldsHolder().getWorldPermissions(player); + for (String groupName : handler.getGroups(player.getName())) { + removeFromGroup(player, groupName); + } + + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "manuadd " + player.getName() + " " + group); + } + + @Override + public List getGroups(Player player) { + final AnjoPermissionsHandler handler = groupManager.getWorldsHolder().getWorldPermissions(player); + if (handler == null) + return new ArrayList<>(); + return Arrays.asList(handler.getGroups(player.getName())); + } + + @Override + public String getPrimaryGroup(Player player) { + final AnjoPermissionsHandler handler = groupManager.getWorldsHolder().getWorldPermissions(player); + if (handler == null) + return null; + return handler.getGroup(player.getName()); + } + + @Override + public PermissionsSystemType getPermissionSystem() { + return PermissionsSystemType.ESSENTIALS_GROUP_MANAGER; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java new file mode 100644 index 00000000..5d467cea --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java @@ -0,0 +1,99 @@ +package fr.xephi.authme.permission.handlers; + +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import org.bukkit.entity.Player; + +import java.util.List; + + +public interface PermissionHandler { + + /** + * Add the permission group of a player, if supported. + * + * @param player The player + * @param group The name of the group. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + boolean addToGroup(Player player, String group); + + /** + * Check whether the current permissions system has group support. + * If no permissions system is hooked, false will be returned. + * + * @return True if the current permissions system supports groups, false otherwise. + */ + boolean hasGroupSupport(); + + /** + * Check if a player has permission. + * + * @param player The player. + * @param node The permission node. + * + * @return True if the player has permission. + */ + boolean hasPermission(Player player, PermissionNode node); + + /** + * Check whether the player is in the specified group. + * + * @param player The player. + * @param group The group name. + * + * @return True if the player is in the specified group, false otherwise. + * False is also returned if groups aren't supported by the used permissions system. + */ + boolean isInGroup(Player player, String group); + + /** + * Remove the permission group of a player, if supported. + * + * @param player The player + * @param group The name of the group. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + boolean removeFromGroup(Player player, String group); + + /** + * Set the permission group of a player, if supported. + * This clears the current groups of the player. + * + * @param player The player + * @param group The name of the group. + * + * @return True if succeed, false otherwise. + * False is also returned if this feature isn't supported for the current permissions system. + */ + boolean setGroup(Player player, String group); + + /** + * Get the permission groups of a player, if available. + * + * @param player The player. + * + * @return Permission groups, or an empty list if this feature is not supported. + */ + List getGroups(Player player); + + /** + * Get the primary group of a player, if available. + * + * @param player The player. + * + * @return The name of the primary permission group. Or null. + */ + String getPrimaryGroup(Player player); + + /** + * Get the permission system that is being used. + * + * @return The permission system. + */ + PermissionsSystemType getPermissionSystem(); +} diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java new file mode 100644 index 00000000..fb1edcae --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java @@ -0,0 +1,68 @@ +package fr.xephi.authme.permission.handlers; + +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +public class PermissionsBukkitHandler implements PermissionHandler { + + @Override + public boolean addToGroup(Player player, String group) { + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + group); + } + + @Override + public boolean hasGroupSupport() { + return true; + } + + @Override + public boolean hasPermission(Player player, PermissionNode node) { + return player.hasPermission(node.getNode()); + } + + @Override + public boolean isInGroup(Player player, String group) { + List groupNames = getGroups(player); + + return groupNames.contains(group); + } + + @Override + public boolean removeFromGroup(Player player, String 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); + } + + @Override + public List getGroups(Player player) { + // FIXME Gnat008 20160601: Add support for this + return new ArrayList<>(); + } + + @Override + public String getPrimaryGroup(Player player) { + // Get the groups of the player + List groups = getGroups(player); + + // Make sure there is any group available, or return null + if (groups.size() == 0) + return null; + + // Return the first group + return groups.get(0); + } + + @Override + public PermissionsSystemType getPermissionSystem() { + return PermissionsSystemType.PERMISSIONS_BUKKIT; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java new file mode 100644 index 00000000..b49e4b37 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java @@ -0,0 +1,89 @@ +package fr.xephi.authme.permission.handlers; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import org.bukkit.entity.Player; +import ru.tehkode.permissions.PermissionManager; +import ru.tehkode.permissions.PermissionUser; +import ru.tehkode.permissions.bukkit.PermissionsEx; + +import java.util.ArrayList; +import java.util.List; + +public class PermissionsExHandler implements PermissionHandler { + + private PermissionManager permissionManager; + + public PermissionsExHandler(PermissionManager permissionManager) { + this.permissionManager = permissionManager; + } + + @Override + public boolean addToGroup(Player player, String group) { + if (!PermissionsEx.getPermissionManager().getGroupNames().contains(group)) { + ConsoleLogger.showError("The plugin tried to set " + player + "'s group to '" + group + "', but it doesn't exist!"); + return false; + } + + PermissionUser user = PermissionsEx.getUser(player); + user.addGroup(group); + return true; + } + + @Override + public boolean hasGroupSupport() { + return true; + } + + @Override + public boolean hasPermission(Player player, PermissionNode node) { + PermissionUser user = permissionManager.getUser(player); + return user.has(node.getNode()); + } + + @Override + public boolean isInGroup(Player player, String group) { + PermissionUser user = permissionManager.getUser(player); + return user.inGroup(group); + } + + @Override + public boolean removeFromGroup(Player player, String group) { + PermissionUser user = permissionManager.getUser(player); + user.removeGroup(group); + return true; + } + + @Override + public boolean setGroup(Player player, String group) { + List groups = new ArrayList<>(); + groups.add(group); + + PermissionUser user = permissionManager.getUser(player); + user.setParentsIdentifier(groups); + return true; + } + + @Override + public List getGroups(Player player) { + PermissionUser user = permissionManager.getUser(player); + return user.getParentIdentifiers(null); + } + + @Override + public String getPrimaryGroup(Player player) { + PermissionUser user = permissionManager.getUser(player); + + List groups = user.getParentIdentifiers(null); + if (groups.size() == 0) + return null; + + return groups.get(0); + } + + @Override + public PermissionsSystemType getPermissionSystem() { + return PermissionsSystemType.PERMISSIONS_EX; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java new file mode 100644 index 00000000..7b99b5ed --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java @@ -0,0 +1,67 @@ +package fr.xephi.authme.permission.handlers; + +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.List; + +public class VaultHandler implements PermissionHandler { + + private Permission vaultProvider; + + public VaultHandler(Permission vaultProvider) { + this.vaultProvider = vaultProvider; + } + + @Override + public boolean addToGroup(Player player, String group) { + return vaultProvider.playerAddGroup(player, group); + } + + @Override + public boolean hasGroupSupport() { + return vaultProvider.hasGroupSupport(); + } + + @Override + public boolean hasPermission(Player player, PermissionNode node) { + return vaultProvider.has(player, node.getNode()); + } + + @Override + public boolean isInGroup(Player player, String group) { + return vaultProvider.playerInGroup(player, group); + } + + @Override + public boolean removeFromGroup(Player player, String group) { + return vaultProvider.playerRemoveGroup(player, group); + } + + @Override + public boolean setGroup(Player player, String group) { + for (String groupName : getGroups(player)) { + removeFromGroup(player, groupName); + } + + return vaultProvider.playerAddGroup(player, group); + } + + @Override + public List getGroups(Player player) { + return Arrays.asList(vaultProvider.getPlayerGroups(player)); + } + + @Override + public String getPrimaryGroup(Player player) { + return vaultProvider.getPrimaryGroup(player); + } + + @Override + public PermissionsSystemType getPermissionSystem() { + return PermissionsSystemType.VAULT; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java new file mode 100644 index 00000000..fb2490f7 --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java @@ -0,0 +1,71 @@ +package fr.xephi.authme.permission.handlers; + +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsSystemType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ZPermissionsHandler implements PermissionHandler { + + private ZPermissionsService zPermissionsService; + + public ZPermissionsHandler(ZPermissionsService zPermissionsService) { + this.zPermissionsService = zPermissionsService; + } + + @Override + public boolean addToGroup(Player player, String group) { + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + group); + } + + @Override + public boolean hasGroupSupport() { + return true; + } + + @Override + public boolean hasPermission(Player player, PermissionNode node) { + Map perms = zPermissionsService.getPlayerPermissions(player.getWorld().getName(), null, player.getName()); + if (perms.containsKey(node.getNode())) + return perms.get(node.getNode()); + else + return node.getDefaultPermission().evaluate(player); + } + + @Override + public boolean isInGroup(Player player, String group) { + return getGroups(player).contains(group); + } + + @Override + public boolean removeFromGroup(Player player, String 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); + } + + @Override + public List getGroups(Player player) { + // TODO Gnat008 20160631: Use UUID not name? + return new ArrayList(zPermissionsService.getPlayerGroups(player.getName())); + } + + @Override + public String getPrimaryGroup(Player player) { + // TODO Gnat008 20160631: Use UUID not name? + return zPermissionsService.getPlayerPrimaryGroup(player.getName()); + } + + @Override + public PermissionsSystemType getPermissionSystem() { + return PermissionsSystemType.Z_PERMISSIONS; + } +} diff --git a/src/main/java/fr/xephi/authme/process/AsynchronousProcess.java b/src/main/java/fr/xephi/authme/process/AsynchronousProcess.java new file mode 100644 index 00000000..80c06313 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/AsynchronousProcess.java @@ -0,0 +1,10 @@ +package fr.xephi.authme.process; + +/** + * Marker interface for asynchronous AuthMe processes. + *

+ * These processes handle intensive (I/O or otherwise) actions and are + * therefore scheduled to run asynchronously. + */ +public interface AsynchronousProcess { +} diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 4b279c9a..5e72fad7 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -1,8 +1,6 @@ package fr.xephi.authme.process; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.process.changepassword.AsyncChangePassword; import fr.xephi.authme.process.email.AsyncAddEmail; import fr.xephi.authme.process.email.AsyncChangeEmail; import fr.xephi.authme.process.join.AsynchronousJoin; @@ -11,60 +9,122 @@ 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.unregister.AsynchronousUnregister; +import fr.xephi.authme.util.BukkitService; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitScheduler; -/** - */ +import javax.inject.Inject; + + public class Management { - private final AuthMe plugin; - private final BukkitScheduler sched; - private final ProcessService processService; - private final DataSource dataSource; - private final PlayerCache playerCache; + @Inject + private BukkitService bukkitService; + + // Processes + @Inject + private AsyncAddEmail asyncAddEmail; + @Inject + private AsyncChangeEmail asyncChangeEmail; + @Inject + private AsynchronousLogout asynchronousLogout; + @Inject + private AsynchronousQuit asynchronousQuit; + @Inject + private AsynchronousJoin asynchronousJoin; + @Inject + private AsyncRegister asyncRegister; + @Inject + private AsynchronousLogin asynchronousLogin; + @Inject + private AsynchronousUnregister asynchronousUnregister; + @Inject + private AsyncChangePassword asyncChangePassword; + + Management() { } - public Management(AuthMe plugin, ProcessService processService, DataSource dataSource, PlayerCache playerCache) { - this.plugin = plugin; - this.sched = this.plugin.getServer().getScheduler(); - this.processService = processService; - this.dataSource = dataSource; - this.playerCache = playerCache; - } public void performLogin(final Player player, final String password, final boolean forceLogin) { - runTask(new AsynchronousLogin(player, password, forceLogin, plugin, dataSource, processService)); + runTask(new Runnable() { + @Override + public void run() { + asynchronousLogin.login(player, password, forceLogin); + } + }); } public void performLogout(final Player player) { - runTask(new AsynchronousLogout(player, plugin, dataSource, processService)); + runTask(new Runnable() { + @Override + public void run() { + asynchronousLogout.logout(player); + } + }); } - public void performRegister(final Player player, final String password, final String email) { - runTask(new AsyncRegister(player, password, email, plugin, dataSource, playerCache, processService)); + public void performRegister(final Player player, final String password, final String email, final boolean autoLogin) { + runTask(new Runnable() { + @Override + public void run() { + asyncRegister.register(player, password, email, autoLogin); + } + }); } - public void performUnregister(final Player player, final String password, final boolean force) { - runTask(new AsynchronousUnregister(player, password, force, plugin, processService)); + public void performUnregister(final Player player, final String password, final boolean isForce) { + runTask(new Runnable() { + @Override + public void run() { + asynchronousUnregister.unregister(player, password, isForce); + } + }); } public void performJoin(final Player player) { - runTask(new AsynchronousJoin(player, plugin, dataSource, playerCache, processService)); + runTask(new Runnable() { + @Override + public void run() { + asynchronousJoin.processJoin(player); + } + }); } public void performQuit(final Player player, final boolean isKick) { - runTask(new AsynchronousQuit(player, plugin, dataSource, isKick, processService)); + runTask(new Runnable() { + @Override + public void run() { + asynchronousQuit.processQuit(player, isKick); + } + }); } public void performAddEmail(final Player player, final String newEmail) { - runTask(new AsyncAddEmail(player, newEmail, dataSource, playerCache, processService)); + runTask(new Runnable() { + @Override + public void run() { + asyncAddEmail.addEmail(player, newEmail); + } + }); } public void performChangeEmail(final Player player, final String oldEmail, final String newEmail) { - runTask(new AsyncChangeEmail(player, oldEmail, newEmail, dataSource, playerCache, processService)); + runTask(new Runnable() { + @Override + public void run() { + asyncChangeEmail.changeEmail(player, oldEmail, newEmail); + } + }); } - private void runTask(Process process) { - sched.runTaskAsynchronously(plugin, process); + public void performPasswordChange(final Player player, final String oldPassword, final String newPassword) { + runTask(new Runnable() { + @Override + public void run() { + asyncChangePassword.changePassword(player, oldPassword, newPassword); + } + }); + } + + private void runTask(Runnable runnable) { + bukkitService.runTaskAsynchronously(runnable); } } diff --git a/src/main/java/fr/xephi/authme/process/Process.java b/src/main/java/fr/xephi/authme/process/Process.java deleted file mode 100644 index d6efd2b2..00000000 --- a/src/main/java/fr/xephi/authme/process/Process.java +++ /dev/null @@ -1,8 +0,0 @@ -package fr.xephi.authme.process; - -/** - * Common interface for AuthMe processes. - */ -public interface Process extends Runnable { - -} diff --git a/src/main/java/fr/xephi/authme/process/ProcessService.java b/src/main/java/fr/xephi/authme/process/ProcessService.java index 653be156..fd2e5657 100644 --- a/src/main/java/fr/xephi/authme/process/ProcessService.java +++ b/src/main/java/fr/xephi/authme/process/ProcessService.java @@ -1,52 +1,38 @@ package fr.xephi.authme.process; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.permission.AuthGroupHandler; +import fr.xephi.authme.permission.AuthGroupType; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.domain.Property; -import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.ValidationService; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.scheduler.BukkitTask; -import java.util.Collection; +import javax.inject.Inject; /** * Service for asynchronous and synchronous processes. */ public class ProcessService { - private final NewSetting settings; - private final Messages messages; - private final AuthMe authMe; - private final DataSource dataSource; - private final PasswordSecurity passwordSecurity; - private final PluginHooks pluginHooks; - private final SpawnLoader spawnLoader; - private final ValidationService validationService; - private final BukkitService bukkitService; + @Inject + private NewSetting settings; - public ProcessService(NewSetting settings, Messages messages, AuthMe authMe, DataSource dataSource, - PasswordSecurity passwordSecurity, PluginHooks pluginHooks, SpawnLoader spawnLoader, - ValidationService validationService, BukkitService bukkitService) { - this.settings = settings; - this.messages = messages; - this.authMe = authMe; - this.dataSource = dataSource; - this.passwordSecurity = passwordSecurity; - this.pluginHooks = pluginHooks; - this.spawnLoader = spawnLoader; - this.validationService = validationService; - this.bukkitService = bukkitService; - } + @Inject + private Messages messages; + + @Inject + private ValidationService validationService; + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private AuthGroupHandler authGroupHandler; /** * Retrieve a property's value. @@ -109,104 +95,6 @@ public class ProcessService { return messages.retrieveSingle(key); } - /** - * Run a task. - * - * @param task the task to run - * @return the assigned task id - */ - public BukkitTask runTask(Runnable task) { - return authMe.getServer().getScheduler().runTask(authMe, task); - } - - /** - * Run a task at a later time. - * - * @param task the task to run - * @param delay the delay before running the task - * @return the assigned task id - */ - public BukkitTask runTaskLater(Runnable task, long delay) { - return authMe.getServer().getScheduler().runTaskLater(authMe, task, delay); - } - - /** - * Schedule a synchronous delayed task. - * - * @param task the task to schedule - * @return the task id - */ - public int scheduleSyncDelayedTask(Runnable task) { - return authMe.getServer().getScheduler().scheduleSyncDelayedTask(authMe, task); - } - - /** - * Emit an event. - * - * @param event the event to emit - */ - public void callEvent(Event event) { - authMe.getServer().getPluginManager().callEvent(event); - } - - /** - * Return the plugin instance. - * - * @return AuthMe instance - */ - public AuthMe getAuthMe() { - return authMe; - } - - /** - * Compute the hash for the given password. - * - * @param password the password to hash - * @param username the user to hash for - * @return the resulting hash - */ - public HashedPassword computeHash(String password, String username) { - return passwordSecurity.computeHash(password, username); - } - - /** - * Return the PluginHooks manager. - * - * @return PluginHooks instance - */ - public PluginHooks getPluginHooks() { - return pluginHooks; - } - - /** - * Return the spawn manager. - * - * @return SpawnLoader instance - */ - public SpawnLoader getSpawnLoader() { - return spawnLoader; - } - - /** - * Return the plugin's datasource. - * - * @return the datasource - */ - public DataSource getDataSource() { - return dataSource; - } - - /** - * Verifies whether a password is valid according to the plugin settings. - * - * @param password the password to verify - * @param username the username the password is associated with - * @return message key with the password error, or {@code null} if password is valid - */ - public MessageKey validatePassword(String password, String username) { - return validationService.validatePassword(password, username); - } - public boolean validateEmail(String email) { return validationService.validateEmail(email); } @@ -215,12 +103,12 @@ public class ProcessService { return validationService.isEmailFreeForRegistration(email, sender); } - public Collection getOnlinePlayers() { - return bukkitService.getOnlinePlayers(); + public boolean hasPermission(Player player, PermissionNode node) { + return permissionsManager.hasPermission(player, node); } - public BukkitService getBukkitService() { - return bukkitService; + public boolean setGroup(Player player, AuthGroupType group) { + return authGroupHandler.setGroup(player, group); } } diff --git a/src/main/java/fr/xephi/authme/process/SyncProcessManager.java b/src/main/java/fr/xephi/authme/process/SyncProcessManager.java new file mode 100644 index 00000000..221e1e8c --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/SyncProcessManager.java @@ -0,0 +1,86 @@ +package fr.xephi.authme.process; + +import fr.xephi.authme.process.login.ProcessSyncPlayerLogin; +import fr.xephi.authme.process.logout.ProcessSynchronousPlayerLogout; +import fr.xephi.authme.process.quit.ProcessSyncronousPlayerQuit; +import fr.xephi.authme.process.register.ProcessSyncEmailRegister; +import fr.xephi.authme.process.register.ProcessSyncPasswordRegister; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Manager for scheduling synchronous processes internally from the asynchronous processes. + * These synchronous processes are a continuation of the associated async processes; they only + * contain certain tasks which may only be run synchronously (most interactions with Bukkit). + * These synchronous tasks should never be called aside from the asynchronous processes. + * + * @see Management + */ +public class SyncProcessManager { + + @Inject + private BukkitService bukkitService; + + @Inject + private ProcessSyncEmailRegister processSyncEmailRegister; + @Inject + private ProcessSyncPasswordRegister processSyncPasswordRegister; + @Inject + private ProcessSyncPlayerLogin processSyncPlayerLogin; + @Inject + private ProcessSynchronousPlayerLogout processSynchronousPlayerLogout; + @Inject + private ProcessSyncronousPlayerQuit processSyncronousPlayerQuit; + + + public void processSyncEmailRegister(final Player player) { + runTask(new Runnable() { + @Override + public void run() { + processSyncEmailRegister.processEmailRegister(player); + } + }); + } + + public void processSyncPasswordRegister(final Player player) { + runTask(new Runnable() { + @Override + public void run() { + processSyncPasswordRegister.processPasswordRegister(player); + } + }); + } + + public void processSyncPlayerLogout(final Player player) { + runTask(new Runnable() { + @Override + public void run() { + processSynchronousPlayerLogout.processSyncLogout(player); + } + }); + } + + public void processSyncPlayerLogin(final Player player) { + runTask(new Runnable() { + @Override + public void run() { + processSyncPlayerLogin.processPlayerLogin(player); + } + }); + } + + public void processSyncPlayerQuit(final Player player, final boolean isOp, final boolean needToChange) { + runTask(new Runnable() { + @Override + public void run() { + processSyncronousPlayerQuit.processSyncQuit(player, isOp, needToChange); + } + }); + } + + private void runTask(Runnable runnable) { + bukkitService.scheduleSyncDelayedTask(runnable); + } +} diff --git a/src/main/java/fr/xephi/authme/process/SynchronousProcess.java b/src/main/java/fr/xephi/authme/process/SynchronousProcess.java new file mode 100644 index 00000000..6c23a0f8 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/SynchronousProcess.java @@ -0,0 +1,10 @@ +package fr.xephi.authme.process; + +/** + * Marker interface for synchronous processes. + *

+ * Such processes are scheduled by {@link AsynchronousProcess asynchronous tasks} to perform tasks + * which are required to be executed synchronously (e.g. interactions with the Bukkit API). + */ +public interface SynchronousProcess { +} diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/process/changepassword/AsyncChangePassword.java similarity index 53% rename from src/main/java/fr/xephi/authme/task/ChangePasswordTask.java rename to src/main/java/fr/xephi/authme/process/changepassword/AsyncChangePassword.java index c5ff5b95..4389ac35 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/process/changepassword/AsyncChangePassword.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.task; +package fr.xephi.authme.process.changepassword; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; @@ -6,51 +6,60 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; +import fr.xephi.authme.process.AsynchronousProcess; +import fr.xephi.authme.process.ProcessService; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.util.BukkitService; import org.bukkit.entity.Player; -public class ChangePasswordTask implements Runnable { +import javax.inject.Inject; - private final AuthMe plugin; - private final Player player; - private final String oldPassword; - private final String newPassword; +public class AsyncChangePassword implements AsynchronousProcess { - public ChangePasswordTask(AuthMe plugin, Player player, String oldPassword, String newPassword) { - this.plugin = plugin; - this.player = player; - this.oldPassword = oldPassword; - this.newPassword = newPassword; - } + @Inject + private AuthMe plugin; - @Override - public void run() { - Messages m = plugin.getMessages(); - PasswordSecurity passwordSecurity = plugin.getPasswordSecurity(); + @Inject + private DataSource dataSource; + @Inject + private ProcessService processService; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private PlayerCache playerCache; + + @Inject + private BukkitService bukkitService; + + AsyncChangePassword() { } + + + public void changePassword(final Player player, String oldPassword, String newPassword) { final String name = player.getName().toLowerCase(); - PlayerAuth auth = PlayerCache.getInstance().getAuth(name); + PlayerAuth auth = playerCache.getAuth(name); if (passwordSecurity.comparePassword(oldPassword, auth.getPassword(), player.getName())) { HashedPassword hashedPassword = passwordSecurity.computeHash(newPassword, name); auth.setPassword(hashedPassword); - if (!plugin.getDataSource().updatePassword(auth)) { - m.send(player, MessageKey.ERROR); + if (!dataSource.updatePassword(auth)) { + processService.send(player, MessageKey.ERROR); return; } - PlayerCache.getInstance().updatePlayer(auth); - m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); + playerCache.updatePlayer(auth); + processService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); ConsoleLogger.info(player.getName() + " changed his password"); - if (Settings.bungee) { + if (processService.getProperty(HooksSettings.BUNGEECORD)) { final String hash = hashedPassword.getHash(); final String salt = hashedPassword.getSalt(); - plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ - + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { ByteArrayDataOutput out = ByteStreams.newDataOutput(); @@ -63,7 +72,7 @@ public class ChangePasswordTask implements Runnable { }); } } else { - m.send(player, MessageKey.WRONG_PASSWORD); + processService.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java index 282f8f09..d7aa3127 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncAddEmail.java @@ -5,33 +5,30 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; import fr.xephi.authme.settings.properties.RegistrationSettings; import org.bukkit.entity.Player; +import javax.inject.Inject; + /** * Async task to add an email to an account. */ -public class AsyncAddEmail implements Process { +public class AsyncAddEmail implements AsynchronousProcess { - private final Player player; - private final String email; - private final ProcessService service; - private final DataSource dataSource; - private final PlayerCache playerCache; + @Inject + private ProcessService service; - public AsyncAddEmail(Player player, String email, DataSource dataSource, PlayerCache playerCache, - ProcessService service) { - this.player = player; - this.email = email; - this.dataSource = dataSource; - this.playerCache = playerCache; - this.service = service; - } + @Inject + private DataSource dataSource; - @Override - public void run() { + @Inject + private PlayerCache playerCache; + + AsyncAddEmail() { } + + public void addEmail(Player player, String email) { String playerName = player.getName().toLowerCase(); if (playerCache.isAuthenticated(playerName)) { @@ -55,11 +52,11 @@ public class AsyncAddEmail implements Process { } } } else { - sendUnloggedMessage(dataSource); + sendUnloggedMessage(player); } } - private void sendUnloggedMessage(DataSource dataSource) { + private void sendUnloggedMessage(Player player) { if (dataSource.isAuthAvailable(player.getName())) { service.send(player, MessageKey.LOGIN_MESSAGE); } else if (service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)) { diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 7489327b..3ca6a246 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -4,35 +4,30 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; import fr.xephi.authme.settings.properties.RegistrationSettings; import org.bukkit.entity.Player; +import javax.inject.Inject; + /** * Async task for changing the email. */ -public class AsyncChangeEmail implements Process { +public class AsyncChangeEmail implements AsynchronousProcess { - private final Player player; - private final String oldEmail; - private final String newEmail; - private final ProcessService service; - private final PlayerCache playerCache; - private final DataSource dataSource; + @Inject + private ProcessService service; - public AsyncChangeEmail(Player player, String oldEmail, String newEmail, DataSource dataSource, - PlayerCache playerCache, ProcessService service) { - this.player = player; - this.oldEmail = oldEmail; - this.newEmail = newEmail; - this.playerCache = playerCache; - this.dataSource = dataSource; - this.service = service; - } + @Inject + private PlayerCache playerCache; - @Override - public void run() { + @Inject + private DataSource dataSource; + + AsyncChangeEmail() { } + + public void changeEmail(Player player, String oldEmail, String newEmail) { String playerName = player.getName().toLowerCase(); if (playerCache.isAuthenticated(playerName)) { PlayerAuth auth = playerCache.getAuth(playerName); @@ -47,14 +42,14 @@ public class AsyncChangeEmail implements Process { } else if (!service.isEmailFreeForRegistration(newEmail, player)) { service.send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); } else { - saveNewEmail(auth); + saveNewEmail(auth, player, newEmail); } } else { - outputUnloggedMessage(); + outputUnloggedMessage(player); } } - private void saveNewEmail(PlayerAuth auth) { + private void saveNewEmail(PlayerAuth auth, Player player, String newEmail) { auth.setEmail(newEmail); if (dataSource.updateEmail(auth)) { playerCache.updatePlayer(auth); @@ -64,7 +59,7 @@ public class AsyncChangeEmail implements Process { } } - private void outputUnloggedMessage() { + private void outputUnloggedMessage(Player player) { if (dataSource.isAuthAvailable(player.getName())) { service.send(player, MessageKey.LOGIN_MESSAGE); } else if (service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)) { 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 37b0dfbd..cf1741c1 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -6,75 +6,101 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.events.FirstSpawnTeleportEvent; import fr.xephi.authme.events.ProtectInventoryEvent; -import fr.xephi.authme.events.SpawnTeleportEvent; -import fr.xephi.authme.listener.AuthMePlayerListener; +import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; -import fr.xephi.authme.settings.Settings; 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.settings.properties.SecuritySettings; -import fr.xephi.authme.task.MessageTask; -import fr.xephi.authme.task.TimeoutTask; +import fr.xephi.authme.task.LimboPlayerTaskManager; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.TeleportationService; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.Utils.GroupType; import org.apache.commons.lang.reflect.MethodUtils; -import org.bukkit.Location; -import org.bukkit.Material; +import org.bukkit.GameMode; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitTask; + +import javax.inject.Inject; import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; +import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; -/** - */ -public class AsynchronousJoin implements Process { - private final AuthMe plugin; - private final Player player; - private final DataSource database; - private final String name; - private final ProcessService service; - private final PlayerCache playerCache; +public class AsynchronousJoin implements AsynchronousProcess { - private final boolean disableCollisions = MethodUtils - .getAccessibleMethod(LivingEntity.class, "setCollidable", new Class[]{}) != null; + private static final boolean DISABLE_COLLISIONS = MethodUtils + .getAccessibleMethod(LivingEntity.class, "setCollidable", new Class[]{}) != null; - public AsynchronousJoin(Player player, AuthMe plugin, DataSource database, PlayerCache playerCache, - ProcessService service) { - this.player = player; - this.plugin = plugin; - this.database = database; - this.name = player.getName().toLowerCase(); - this.service = service; - this.playerCache = playerCache; - } + @Inject + private AuthMe plugin; - @Override - public void run() { - if (Utils.isUnrestricted(player)) { + @Inject + private DataSource database; + + @Inject + private ProcessService service; + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboCache limboCache; + + @Inject + private PluginHooks pluginHooks; + + @Inject + private TeleportationService teleportationService; + + @Inject + private BukkitService bukkitService; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + AsynchronousJoin() { } + + + public void processJoin(final Player player) { + final String name = player.getName().toLowerCase(); + final String ip = Utils.getPlayerIp(player); + + if (isPlayerUnrestricted(name)) { return; } - if (service.getProperty(HooksSettings.DISABLE_SOCIAL_SPY)) { - service.getPluginHooks().setEssentialsSocialSpyStatus(player, false); + // Prevent player collisions in 1.9 + if (DISABLE_COLLISIONS) { + player.setCollidable(false); } - final String ip = Utils.getPlayerIp(player); - if (isNameRestricted(name, ip, player.getAddress().getHostName())) { - service.scheduleSyncDelayedTask(new Runnable() { + if (service.getProperty(RestrictionSettings.FORCE_SURVIVAL_MODE) + && !service.hasPermission(player, PlayerStatePermission.BYPASS_FORCE_SURVIVAL)) { + bukkitService.runTask(new Runnable() { + @Override + public void run() { + player.setGameMode(GameMode.SURVIVAL); + } + }); + } + + if (service.getProperty(HooksSettings.DISABLE_SOCIAL_SPY)) { + pluginHooks.setEssentialsSocialSpyStatus(player, false); + } + + if (isNameRestricted(name, ip, player.getAddress().getHostName())) { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { - AuthMePlayerListener.causeByAuthMe.putIfAbsent(name, true); player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR)); if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) { plugin.getServer().banIP(ip); @@ -83,48 +109,22 @@ public class AsynchronousJoin implements Process { }); return; } - if (service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP) > 0 - && !plugin.getPermissionsManager().hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) - && !"127.0.0.1".equalsIgnoreCase(ip) - && !"localhost".equalsIgnoreCase(ip) - && hasJoinedIp(player.getName(), ip)) { - service.scheduleSyncDelayedTask(new Runnable() { - @Override - public void run() { - player.kickPlayer("A player with the same IP is already in game!"); - } - }); + + if (!validatePlayerCountForIp(player, ip)) { return; } - // Prevent player collisions in 1.9 - if (disableCollisions) { - ((LivingEntity) player).setCollidable(false); - } - final Location spawnLoc = service.getSpawnLoader().getSpawnLocation(player); - final boolean isAuthAvailable = database.isAuthAvailable(name); - if (isAuthAvailable) { - if (!service.getProperty(RestrictionSettings.NO_TELEPORT)) { - if (Settings.isTeleportToSpawnEnabled || (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName()))) { - service.scheduleSyncDelayedTask(new Runnable() { - @Override - public void run() { - SpawnTeleportEvent tpEvent = new SpawnTeleportEvent(player, player.getLocation(), spawnLoc, playerCache.isAuthenticated(name)); - service.callEvent(tpEvent); - if (!tpEvent.isCancelled() && player.isOnline() && tpEvent.getTo() != null - && tpEvent.getTo().getWorld() != null) { - player.teleport(tpEvent.getTo()); - } - } - }); - } - } - placePlayerSafely(player, spawnLoc); - LimboCache.getInstance().updateLimboPlayer(player); - // protect inventory + final boolean isAuthAvailable = database.isAuthAvailable(name); + + if (isAuthAvailable) { + service.setGroup(player, AuthGroupType.NOT_LOGGED_IN); + teleportationService.teleportOnJoin(player); + limboCache.updateLimboPlayer(player); + + // Protect inventory if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN) && plugin.inventoryProtector != null) { ProtectInventoryEvent ev = new ProtectInventoryEvent(player); - plugin.getServer().getPluginManager().callEvent(ev); + bukkitService.callEvent(ev); if (ev.isCancelled()) { plugin.inventoryProtector.sendInventoryPacket(player); if (!service.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { @@ -133,6 +133,7 @@ public class AsynchronousJoin implements Process { } } + // Session logic if (service.getProperty(PluginSettings.SESSIONS_ENABLED) && (playerCache.isAuthenticated(name) || database.isLogged(name))) { if (plugin.sessions.containsKey(name)) { plugin.sessions.get(name).cancel(); @@ -140,7 +141,7 @@ public class AsynchronousJoin implements Process { } PlayerAuth auth = database.getAuth(name); database.setUnlogged(name); - PlayerCache.getInstance().removePlayer(name); + playerCache.removePlayer(name); if (auth != null && auth.getIp().equals(ip)) { service.send(player, MessageKey.SESSION_RECONNECTION); plugin.getManagement().performLogin(player, "dontneed", true); @@ -150,37 +151,27 @@ public class AsynchronousJoin implements Process { } } } else { - if (!Settings.unRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.UNREGISTERED); - } + // Not Registered + + // Groups logic + service.setGroup(player, AuthGroupType.UNREGISTERED); + + // Skip if registration is optional if (!service.getProperty(RegistrationSettings.FORCE)) { return; } - if (!Settings.noTeleport && !needFirstSpawn() && Settings.isTeleportToSpawnEnabled - || (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName()))) { - service.scheduleSyncDelayedTask(new Runnable() { - @Override - public void run() { - SpawnTeleportEvent tpEvent = new SpawnTeleportEvent(player, player.getLocation(), spawnLoc, PlayerCache.getInstance().isAuthenticated(name)); - service.callEvent(tpEvent); - if (!tpEvent.isCancelled() && player.isOnline() && tpEvent.getTo() != null - && tpEvent.getTo().getWorld() != null) { - player.teleport(tpEvent.getTo()); - } - } - }); - } + teleportationService.teleportOnJoin(player); + } + // The user is not logged in + + if (!limboCache.hasLimboPlayer(name)) { + limboCache.addLimboPlayer(player); } - if (!LimboCache.getInstance().hasLimboPlayer(name)) { - LimboCache.getInstance().addLimboPlayer(player); - } - Utils.setGroup(player, isAuthAvailable ? GroupType.NOTLOGGEDIN : GroupType.UNREGISTERED); + final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * 20; - - service.scheduleSyncDelayedTask(new Runnable() { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { player.setOp(false); @@ -190,7 +181,7 @@ public class AsynchronousJoin implements Process { player.setWalkSpeed(0.0f); } player.setNoDamageTicks(registrationTimeout); - if (plugin.getPluginHooks().isEssentialsAvailable() && service.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)) { + if (pluginHooks.isEssentialsAvailable() && service.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)) { player.performCommand("motd"); } if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { @@ -202,79 +193,17 @@ public class AsynchronousJoin implements Process { }); - int msgInterval = service.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - if (registrationTimeout > 0) { - BukkitTask id = service.runTaskLater(new TimeoutTask(plugin, name, player), registrationTimeout); - LimboCache.getInstance().getLimboPlayer(name).setTimeoutTask(id); - } - - MessageKey msg; - if (isAuthAvailable) { - msg = MessageKey.LOGIN_MESSAGE; - } else { - msg = service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION) - ? MessageKey.REGISTER_EMAIL_MESSAGE - : MessageKey.REGISTER_MESSAGE; - } - if (msgInterval > 0 && LimboCache.getInstance().getLimboPlayer(name) != null) { - BukkitTask msgTask = service.runTask(new MessageTask(service.getBukkitService(), plugin.getMessages(), - name, msg, msgInterval)); - LimboCache.getInstance().getLimboPlayer(name).setMessageTask(msgTask); - } + // Timeout and message task + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); } - private boolean needFirstSpawn() { - if (player.hasPlayedBefore()) { - return false; - } - Location firstSpawn = service.getSpawnLoader().getFirstSpawn(); - if (firstSpawn == null) { - return false; - } - - FirstSpawnTeleportEvent tpEvent = new FirstSpawnTeleportEvent(player, player.getLocation(), firstSpawn); - plugin.getServer().getPluginManager().callEvent(tpEvent); - if (!tpEvent.isCancelled()) { - if (player.isOnline() && tpEvent.getTo() != null && tpEvent.getTo().getWorld() != null) { - final Location fLoc = tpEvent.getTo(); - service.scheduleSyncDelayedTask(new Runnable() { - @Override - public void run() { - player.teleport(fLoc); - } - }); - } - } - return true; - } - - private void placePlayerSafely(final Player player, final Location spawnLoc) { - if (spawnLoc == null || service.getProperty(RestrictionSettings.NO_TELEPORT)) - return; - if (Settings.isTeleportToSpawnEnabled || (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName()))) - return; - if (!player.hasPlayedBefore()) - return; - service.scheduleSyncDelayedTask(new Runnable() { - @Override - public void run() { - if (spawnLoc.getWorld() == null) { - return; - } - Material cur = player.getLocation().getBlock().getType(); - Material top = player.getLocation().add(0, 1, 0).getBlock().getType(); - if (cur == Material.PORTAL || cur == Material.ENDER_PORTAL - || top == Material.PORTAL || top == Material.ENDER_PORTAL) { - service.send(player, MessageKey.UNSAFE_QUIT_LOCATION); - player.teleport(spawnLoc); - } - } - - }); + private boolean isPlayerUnrestricted(String name) { + return service.getProperty(RestrictionSettings.UNRESTRICTED_NAMES).contains(name); } /** - * Return whether the name is restricted based on the restriction setting. + * 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 @@ -302,14 +231,39 @@ public class AsynchronousJoin implements Process { return nameFound; } - private boolean hasJoinedIp(String name, String ip) { + /** + * Checks whether the maximum number of accounts has been exceeded for the given IP address (according to + * settings and permissions). If this is the case, the player is kicked. + * + * @param player the player to verify + * @param ip the ip address of the player + * @return true if the verification is OK (no infraction), false if player has been kicked + */ + private boolean validatePlayerCountForIp(final Player player, String ip) { + if (service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP) > 0 + && !service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) + && !"127.0.0.1".equalsIgnoreCase(ip) + && !"localhost".equalsIgnoreCase(ip) + && countOnlinePlayersByIp(ip) > service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP)) { + + bukkitService.scheduleSyncDelayedTask(new Runnable() { + @Override + public void run() { + player.kickPlayer(service.retrieveSingleMessage(MessageKey.SAME_IP_ONLINE)); + } + }); + return false; + } + return true; + } + + private int countOnlinePlayersByIp(String ip) { int count = 0; - for (Player player : service.getOnlinePlayers()) { - if (ip.equalsIgnoreCase(Utils.getPlayerIp(player)) - && !player.getName().equalsIgnoreCase(name)) { - count++; + for (Player player : bukkitService.getOnlinePlayers()) { + if (ip.equalsIgnoreCase(Utils.getPlayerIp(player))) { + ++count; } } - return count >= service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP); + return count; } } 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 b7bb3f2b..18e7e8e4 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -1,7 +1,8 @@ package fr.xephi.authme.process.login; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.CaptchaManager; +import fr.xephi.authme.cache.TempbanManager; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; @@ -10,69 +11,81 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.output.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.Process; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; -import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.task.LimboPlayerTaskManager; +import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; +import javax.inject.Inject; +import java.util.ArrayList; import java.util.List; /** */ -public class AsynchronousLogin implements Process { +public class AsynchronousLogin implements AsynchronousProcess { - private final Player player; - private final String name; - private final String realName; - private final String password; - private final boolean forceLogin; - private final AuthMe plugin; - private final DataSource database; - private final String ip; - private final ProcessService service; + @Inject + private DataSource database; - public AsynchronousLogin(Player player, String password, boolean forceLogin, AuthMe plugin, DataSource data, - ProcessService service) { - this.player = player; - this.name = player.getName().toLowerCase(); - this.password = password; - this.realName = player.getName(); - this.forceLogin = forceLogin; - this.plugin = plugin; - this.database = data; - this.ip = Utils.getPlayerIp(player); - this.service = service; - } + @Inject + private ProcessService service; - private boolean needsCaptcha() { - if (service.getProperty(SecuritySettings.USE_CAPTCHA)) { - if (!plugin.captcha.containsKey(name)) { - plugin.captcha.putIfAbsent(name, 1); - } else { - int i = plugin.captcha.get(name) + 1; - plugin.captcha.remove(name); - plugin.captcha.putIfAbsent(name, i); - } - if (plugin.captcha.containsKey(name) && plugin.captcha.get(name) > Settings.maxLoginTry) { - plugin.cap.putIfAbsent(name, RandomString.generate(Settings.captchaLength)); - service.send(player, MessageKey.USAGE_CAPTCHA, plugin.cap.get(name)); - return true; - } - } - return false; + @Inject + private PermissionsManager permissionsManager; + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboCache limboCache; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private BukkitService bukkitService; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private CaptchaManager captchaManager; + + @Inject + private TempbanManager tempbanManager; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + AsynchronousLogin() { } + + + /** + * Queries the {@link fr.xephi.authme.cache.CaptchaManager} to + * see if a captcha needs to be entered in order to log in. + * + * @param player The player to check + * @return True if a captcha needs to be entered + */ + private boolean needsCaptcha(Player player) { + final String playerName = player.getName(); + + return captchaManager.isCaptchaRequired(playerName); } /** @@ -81,8 +94,9 @@ public class AsynchronousLogin implements Process { * * @return PlayerAuth */ - private PlayerAuth preAuth() { - if (PlayerCache.getInstance().isAuthenticated(name)) { + private PlayerAuth preAuth(Player player) { + final String name = player.getName().toLowerCase(); + if (playerCache.isAuthenticated(name)) { service.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return null; } @@ -91,16 +105,8 @@ public class AsynchronousLogin implements Process { if (pAuth == null) { service.send(player, MessageKey.USER_NOT_REGISTERED); - LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); - if (limboPlayer != null) { - limboPlayer.getMessageTask().cancel(); - String[] msg = service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION) - ? service.retrieveMessage(MessageKey.REGISTER_EMAIL_MESSAGE) - : service.retrieveMessage(MessageKey.REGISTER_MESSAGE); - BukkitTask messageTask = service.runTask(new MessageTask(service.getBukkitService(), - name, msg, service.getProperty(RegistrationSettings.MESSAGE_INTERVAL))); - limboPlayer.setMessageTask(messageTask); - } + // TODO ljacqu 20160612: Why is the message task being canceled and added again here? + limboPlayerTaskManager.registerMessageTask(name, false); return null; } @@ -109,10 +115,11 @@ public class AsynchronousLogin implements Process { return null; } - if (Settings.getMaxLoginPerIp > 0 - && !plugin.getPermissionsManager().hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) - && !ip.equalsIgnoreCase("127.0.0.1") && !ip.equalsIgnoreCase("localhost")) { - if (plugin.isLoggedIp(name, ip)) { + final String ip = Utils.getPlayerIp(player); + if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) > 0 + && !permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) + && !"127.0.0.1".equalsIgnoreCase(ip) && !"localhost".equalsIgnoreCase(ip)) { + if (isLoggedIp(name, ip)) { service.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return null; } @@ -126,46 +133,52 @@ public class AsynchronousLogin implements Process { return pAuth; } - @Override - public void run() { - PlayerAuth pAuth = preAuth(); - if (pAuth == null || needsCaptcha()) { + public void login(final Player player, String password, boolean forceLogin) { + PlayerAuth pAuth = preAuth(player); + if (pAuth == null) { return; } + final String name = player.getName().toLowerCase(); + + // If Captcha is required send a message to the player and deny to login + if (needsCaptcha(player)) { + service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(name)); + return; + } + + final String ip = Utils.getPlayerIp(player); + + // Increase the counts here before knowing the result of the login. + // If the login is successful, we clear the captcha count for the player. + captchaManager.increaseCount(name); + tempbanManager.increaseCount(ip); + if ("127.0.0.1".equals(pAuth.getIp()) && !pAuth.getIp().equals(ip)) { pAuth.setIp(ip); database.updateIp(pAuth.getNickname(), ip); } String email = pAuth.getEmail(); - boolean passwordVerified = forceLogin || plugin.getPasswordSecurity() - .comparePassword(password, pAuth.getPassword(), realName); - + boolean passwordVerified = forceLogin || passwordSecurity.comparePassword( + password, pAuth.getPassword(), player.getName()); if (passwordVerified && player.isOnline()) { PlayerAuth auth = PlayerAuth.builder() .name(name) - .realName(realName) + .realName(player.getName()) .ip(ip) .email(email) .password(pAuth.getPassword()) .build(); database.updateSession(auth); - if (service.getProperty(SecuritySettings.USE_CAPTCHA)) { - if (plugin.captcha.containsKey(name)) { - plugin.captcha.remove(name); - } - if (plugin.cap.containsKey(name)) { - plugin.cap.remove(name); - } - } - + captchaManager.resetCounts(name); player.setNoDamageTicks(0); + if (!forceLogin) service.send(player, MessageKey.LOGIN_SUCCESS); - displayOtherAccounts(auth); + displayOtherAccounts(auth, player); if (service.getProperty(EmailSettings.RECALL_PLAYERS) && (StringUtils.isEmpty(email) || "your@email.com".equalsIgnoreCase(email))) { @@ -173,63 +186,102 @@ public class AsynchronousLogin implements Process { } if (!service.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { - ConsoleLogger.info(realName + " logged in!"); + ConsoleLogger.info(player.getName() + " logged in!"); } // makes player isLoggedin via API - PlayerCache.getInstance().addPlayer(auth); + playerCache.addPlayer(auth); database.setLogged(name); // As the scheduling executes the Task most likely after the current // 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. - ProcessSyncPlayerLogin syncPlayerLogin = new ProcessSyncPlayerLogin(player, plugin, database, service); - if (syncPlayerLogin.getLimbo() != null) { - if (syncPlayerLogin.getLimbo().getTimeoutTask() != null) { - syncPlayerLogin.getLimbo().getTimeoutTask().cancel(); + LimboPlayer limboPlayer = limboCache.getLimboPlayer(name); + if (limboPlayer != null) { + if (limboPlayer.getTimeoutTask() != null) { + limboPlayer.getTimeoutTask().cancel(); } - if (syncPlayerLogin.getLimbo().getMessageTask() != null) { - syncPlayerLogin.getLimbo().getMessageTask().cancel(); + if (limboPlayer.getMessageTask() != null) { + limboPlayer.getMessageTask().cancel(); } } - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, syncPlayerLogin); + syncProcessManager.processSyncPlayerLogin(player); } else if (player.isOnline()) { if (!service.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { - ConsoleLogger.info(realName + " used the wrong password"); + ConsoleLogger.info(player.getName() + " used the wrong password"); } if (service.getProperty(RestrictionSettings.KICK_ON_WRONG_PASSWORD)) { - service.scheduleSyncDelayedTask(new Runnable() { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { player.kickPlayer(service.retrieveSingleMessage(MessageKey.WRONG_PASSWORD)); } }); - } else { + } else if (tempbanManager.shouldTempban(ip)) { + tempbanManager.tempbanPlayer(player); + } else { service.send(player, MessageKey.WRONG_PASSWORD); + + // If the authentication fails check if Captcha is required and send a message to the player + if (needsCaptcha(player)) { + service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(name)); + } } } else { ConsoleLogger.showError("Player " + name + " wasn't online during login process, aborted... "); } } - private void displayOtherAccounts(PlayerAuth auth) { + private void displayOtherAccounts(PlayerAuth auth, Player player) { if (!service.getProperty(RestrictionSettings.DISPLAY_OTHER_ACCOUNTS) || auth == null) { return; } - List auths = this.database.getAllAuthsByIp(auth.getIp()); - if (auths.size() < 2) { + List auths = database.getAllAuthsByIp(auth.getIp()); + if (auths.size() <= 1) { return; } - String message = "[AuthMe] " + StringUtils.join(", ", auths) + "."; - for (Player player : service.getOnlinePlayers()) { - if (plugin.getPermissionsManager().hasPermission(player, AdminPermission.SEE_OTHER_ACCOUNTS) - || (player.getName().equals(this.player.getName()) - && plugin.getPermissionsManager().hasPermission(player, PlayerPermission.SEE_OWN_ACCOUNTS))) { - player.sendMessage("[AuthMe] The player " + auth.getNickname() + " has " + auths.size() + " accounts"); - player.sendMessage(message); + + List formattedNames = new ArrayList<>(auths.size()); + for (String currentName : auths) { + Player currentPlayer = bukkitService.getPlayerExact(currentName); + if (currentPlayer != null && currentPlayer.isOnline()) { + formattedNames.add(ChatColor.GREEN + currentName); + } else { + formattedNames.add(currentName); + } + } + + String message = ChatColor.GRAY + StringUtils.join(ChatColor.GRAY + ", ", formattedNames) + "."; + + if (!service.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { + ConsoleLogger.info("The user " + player.getName() + " has " + auths.size() + " accounts:"); + ConsoleLogger.info(message); + } + + for (Player onlinePlayer : bukkitService.getOnlinePlayers()) { + if (onlinePlayer.getName().equalsIgnoreCase(player.getName()) + && permissionsManager.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)) { + service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_OTHER, + player.getName(), Integer.toString(auths.size())); + onlinePlayer.sendMessage(message); } } } + + private boolean isLoggedIp(String name, String ip) { + int count = 0; + for (Player player : bukkitService.getOnlinePlayers()) { + if (ip.equalsIgnoreCase(Utils.getPlayerIp(player)) + && database.isLogged(player.getName().toLowerCase()) + && !player.getName().equalsIgnoreCase(name)) { + ++count; + } + } + return count >= service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP); + } } 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 9f5de6bd..28304e00 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -4,177 +4,121 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.events.AuthMeTeleportEvent; import fr.xephi.authme.events.LoginEvent; import fr.xephi.authme.events.RestoreInventoryEvent; -import fr.xephi.authme.events.SpawnTeleportEvent; import fr.xephi.authme.listener.AuthMePlayerListener; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.ProcessService; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.Utils.GroupType; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.TeleportationService; import org.apache.commons.lang.reflect.MethodUtils; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.potion.PotionEffectType; +import javax.inject.Inject; + import static fr.xephi.authme.settings.properties.PluginSettings.KEEP_COLLISIONS_DISABLED; import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; -public class ProcessSyncPlayerLogin implements Process { +public class ProcessSyncPlayerLogin implements SynchronousProcess { - private final LimboPlayer limbo; - private final Player player; - private final String name; - private final PlayerAuth auth; - private final AuthMe plugin; - private final PluginManager pm; - private final JsonCache playerCache; - private final ProcessService service; + private static final boolean RESTORE_COLLISIONS = MethodUtils + .getAccessibleMethod(LivingEntity.class, "setCollidable", new Class[]{}) != null; - private final boolean restoreCollisions = MethodUtils - .getAccessibleMethod(LivingEntity.class, "setCollidable", new Class[]{}) != null; + @Inject + private AuthMe plugin; - /** - * Constructor for ProcessSyncPlayerLogin. - * - * @param player Player - * @param plugin AuthMe - * @param database DataSource - * @param service The process service - */ - public ProcessSyncPlayerLogin(Player player, AuthMe plugin, DataSource database, ProcessService service) { - this.plugin = plugin; - this.pm = plugin.getServer().getPluginManager(); - this.player = player; - this.name = player.getName().toLowerCase(); - this.limbo = LimboCache.getInstance().getLimboPlayer(name); - this.auth = database.getAuth(name); - this.playerCache = new JsonCache(); - this.service = service; - } + @Inject + private ProcessService service; - public LimboPlayer getLimbo() { - return limbo; - } + @Inject + private LimboCache limboCache; - private void restoreOpState() { - player.setOp(limbo.isOperator()); - } + @Inject + private DataSource dataSource; - private void packQuitLocation() { - Utils.packCoords(auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ(), auth.getWorld(), player); - } + @Inject + private BukkitService bukkitService; - private void teleportBackFromSpawn() { - AuthMeTeleportEvent tpEvent = new AuthMeTeleportEvent(player, limbo.getLoc()); - pm.callEvent(tpEvent); - if (!tpEvent.isCancelled() && tpEvent.getTo() != null) { - player.teleport(tpEvent.getTo()); - } - } + @Inject + private PluginManager pluginManager; - private void teleportToSpawn() { - Location spawnL = plugin.getSpawnLocation(player); - SpawnTeleportEvent tpEvent = new SpawnTeleportEvent(player, player.getLocation(), spawnL, true); - pm.callEvent(tpEvent); - if (!tpEvent.isCancelled() && tpEvent.getTo() != null) { - player.teleport(tpEvent.getTo()); - } - } + @Inject + private TeleportationService teleportationService; - private void restoreSpeedEffects() { - if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) && service.getProperty(RestrictionSettings.REMOVE_SPEED)) { + ProcessSyncPlayerLogin() { } + + + private void restoreSpeedEffects(Player player) { + if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) + && service.getProperty(RestrictionSettings.REMOVE_SPEED)) { player.setWalkSpeed(0.2F); player.setFlySpeed(0.1F); } } - private void restoreInventory() { + private void restoreInventory(Player player) { RestoreInventoryEvent event = new RestoreInventoryEvent(player); - pm.callEvent(event); + pluginManager.callEvent(event); if (!event.isCancelled() && plugin.inventoryProtector != null) { plugin.inventoryProtector.sendInventoryPacket(player); } } - private void forceCommands() { + private void forceCommands(Player player) { for (String command : service.getProperty(RegistrationSettings.FORCE_COMMANDS)) { player.performCommand(command.replace("%p", player.getName())); } for (String command : service.getProperty(RegistrationSettings.FORCE_COMMANDS_AS_CONSOLE)) { - Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), command.replace("%p", player.getName())); + Bukkit.getServer().dispatchCommand( + Bukkit.getServer().getConsoleSender(), command.replace("%p", player.getName())); } } - private void sendBungeeMessage() { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("Forward"); - out.writeUTF("ALL"); - out.writeUTF("AuthMe"); - out.writeUTF("login;" + name); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } - - @Override - public void run() { + public void processPlayerLogin(Player player) { + final String name = player.getName().toLowerCase(); // Limbo contains the State of the Player before /login + final LimboPlayer limbo = limboCache.getLimboPlayer(name); + final PlayerAuth auth = dataSource.getAuth(name); + if (limbo != null) { // Restore Op state and Permission Group - restoreOpState(); - Utils.setGroup(player, GroupType.LOGGEDIN); + restoreOpState(player, limbo); + service.setGroup(player, AuthGroupType.LOGGED_IN); - if (!Settings.noTeleport) { - if (Settings.isTeleportToSpawnEnabled && !Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName())) { - if (Settings.isSaveQuitLocationEnabled && auth.getQuitLocY() != 0) { - packQuitLocation(); - } else { - teleportBackFromSpawn(); - } - } else if (Settings.isForceSpawnLocOnJoinEnabled && Settings.getForcedWorlds.contains(player.getWorld().getName())) { - teleportToSpawn(); - } else if (Settings.isSaveQuitLocationEnabled && auth.getQuitLocY() != 0) { - packQuitLocation(); - } else { - teleportBackFromSpawn(); - } - } + teleportationService.teleportOnLogin(player, auth, limbo); - if (restoreCollisions && !service.getProperty(KEEP_COLLISIONS_DISABLED)) { + if (RESTORE_COLLISIONS && !service.getProperty(KEEP_COLLISIONS_DISABLED)) { player.setCollidable(true); } if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { - restoreInventory(); + restoreInventory(player); } if (service.getProperty(RestrictionSettings.HIDE_TABLIST_BEFORE_LOGIN) && plugin.tablistHider != null) { plugin.tablistHider.sendTablist(player); } - // Cleanup no longer used temporary data - LimboCache.getInstance().deleteLimboPlayer(name); - if (playerCache.doesCacheExist(player)) { - playerCache.removeCache(player); - } + // Clean up no longer used temporary data + limboCache.deleteLimboPlayer(name); } // We can now display the join message (if delayed) String jm = AuthMePlayerListener.joinMessage.get(name); if (jm != null) { if (!jm.isEmpty()) { - for (Player p : service.getOnlinePlayers()) { + for (Player p : bukkitService.getOnlinePlayers()) { if (p.isOnline()) { p.sendMessage(jm); } @@ -183,16 +127,16 @@ public class ProcessSyncPlayerLogin implements Process { AuthMePlayerListener.joinMessage.remove(name); } - restoreSpeedEffects(); + restoreSpeedEffects(player); if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { player.removePotionEffect(PotionEffectType.BLINDNESS); } // The Login event now fires (as intended) after everything is processed - Bukkit.getServer().getPluginManager().callEvent(new LoginEvent(player)); + bukkitService.callEvent(new LoginEvent(player)); player.saveData(); if (service.getProperty(HooksSettings.BUNGEECORD)) { - sendBungeeMessage(); + sendBungeeMessage(player); } // Login is done, display welcome message if (service.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { @@ -208,12 +152,16 @@ public class ProcessSyncPlayerLogin implements Process { } // Login is now finished; we can force all commands - forceCommands(); + forceCommands(player); - sendTo(); + sendTo(player); } - private void sendTo() { + private void restoreOpState(Player player, LimboPlayer limboPlayer) { + player.setOp(limboPlayer.isOperator()); + } + + private void sendTo(Player player) { if (!service.getProperty(HooksSettings.BUNGEECORD_SERVER).isEmpty()) { ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("Connect"); @@ -221,4 +169,14 @@ public class ProcessSyncPlayerLogin implements Process { player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); } } + + private void sendBungeeMessage(Player player) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Forward"); + out.writeUTF("ALL"); + out.writeUTF("AuthMe"); + out.writeUTF("login;" + player.getName()); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + } + } 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 a882ab30..fc81017d 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -1,79 +1,69 @@ package fr.xephi.authme.process.logout; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.permission.AuthGroupType; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.Utils.GroupType; import org.bukkit.entity.Player; -/** - */ -public class AsynchronousLogout implements Process { +import javax.inject.Inject; - private final Player player; - private final String name; - private final AuthMe plugin; - private final DataSource database; - private boolean canLogout = true; - private final ProcessService service; +public class AsynchronousLogout implements AsynchronousProcess { - /** - * Constructor for AsynchronousLogout. - * - * @param player Player - * @param plugin AuthMe - * @param database DataSource - * @param service The process service - */ - public AsynchronousLogout(Player player, AuthMe plugin, DataSource database, ProcessService service) { - this.player = player; - this.plugin = plugin; - this.database = database; - this.name = player.getName().toLowerCase(); - this.service = service; - } + @Inject + private DataSource database; - private void preLogout() { - if (!PlayerCache.getInstance().isAuthenticated(name)) { + @Inject + private ProcessService service; + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboCache limboCache; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private BukkitService bukkitService; + + AsynchronousLogout() { } + + public void logout(final Player player) { + final String name = player.getName().toLowerCase(); + if (!playerCache.isAuthenticated(name)) { service.send(player, MessageKey.NOT_LOGGED_IN); - canLogout = false; - } - } - - @Override - public void run() { - preLogout(); - if (!canLogout) { return; } - final Player p = player; - PlayerAuth auth = PlayerCache.getInstance().getAuth(name); + PlayerAuth auth = playerCache.getAuth(name); database.updateSession(auth); - auth.setQuitLocX(p.getLocation().getX()); - auth.setQuitLocY(p.getLocation().getY()); - auth.setQuitLocZ(p.getLocation().getZ()); - auth.setWorld(p.getWorld().getName()); + auth.setQuitLocX(player.getLocation().getX()); + auth.setQuitLocY(player.getLocation().getY()); + auth.setQuitLocZ(player.getLocation().getZ()); + auth.setWorld(player.getWorld().getName()); database.updateQuitLoc(auth); - PlayerCache.getInstance().removePlayer(name); + playerCache.removePlayer(name); database.setUnlogged(name); - service.scheduleSyncDelayedTask(new Runnable() { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { - Utils.teleportToSpawn(p); + Utils.teleportToSpawn(player); } }); - if (LimboCache.getInstance().hasLimboPlayer(name)) { - LimboCache.getInstance().deleteLimboPlayer(name); + if (limboCache.hasLimboPlayer(name)) { + limboCache.deleteLimboPlayer(name); } - LimboCache.getInstance().addLimboPlayer(player); - Utils.setGroup(player, GroupType.NOTLOGGEDIN); - service.scheduleSyncDelayedTask(new ProcessSynchronousPlayerLogout(p, plugin, service)); + limboCache.addLimboPlayer(player); + service.setGroup(player, AuthGroupType.NOT_LOGGED_IN); + 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 d6d66b90..c70f4cd0 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java @@ -4,91 +4,83 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.events.LogoutEvent; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; import fr.xephi.authme.process.ProcessService; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.process.SynchronousProcess; +import fr.xephi.authme.settings.properties.HooksSettings; 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.Bukkit; +import fr.xephi.authme.task.LimboPlayerTaskManager; +import fr.xephi.authme.util.BukkitService; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitTask; -/** - */ -public class ProcessSynchronousPlayerLogout implements Process { +import javax.inject.Inject; - private final Player player; - private final AuthMe plugin; - private final String name; - private final ProcessService service; +import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; - /** - * Constructor for ProcessSynchronousPlayerLogout. - * - * @param player Player - * @param plugin AuthMe - * @param service The process service - */ - public ProcessSynchronousPlayerLogout(Player player, AuthMe plugin, ProcessService service) { - this.player = player; - this.plugin = plugin; - this.name = player.getName().toLowerCase(); - this.service = service; - } - protected void sendBungeeMessage() { +public class ProcessSynchronousPlayerLogout implements SynchronousProcess { + + @Inject + private AuthMe plugin; + + @Inject + private ProcessService service; + + @Inject + private BukkitService bukkitService; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + ProcessSynchronousPlayerLogout() { } + + + private void sendBungeeMessage(Player player) { ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("Forward"); out.writeUTF("ALL"); out.writeUTF("AuthMe"); - out.writeUTF("logout;" + name); + out.writeUTF("logout;" + player.getName()); player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); } - protected void restoreSpeedEffect() { - if (Settings.isRemoveSpeedEnabled) { + private void restoreSpeedEffect(Player player) { + if (service.getProperty(RestrictionSettings.REMOVE_SPEED)) { player.setWalkSpeed(0.0F); player.setFlySpeed(0.0F); } } - @Override - public void run() { + public void processSyncLogout(Player player) { + final String name = player.getName().toLowerCase(); if (plugin.sessions.containsKey(name)) { plugin.sessions.get(name).cancel(); plugin.sessions.remove(name); } - if (Settings.protectInventoryBeforeLogInEnabled) { + if (service.getProperty(RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN)) { plugin.inventoryProtector.sendBlankInventoryPacket(player); } - int timeOut = service.getProperty(RestrictionSettings.TIMEOUT) * 20; - int interval = service.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - if (timeOut != 0) { - BukkitTask id = service.runTaskLater(new TimeoutTask(plugin, name, player), timeOut); - LimboCache.getInstance().getLimboPlayer(name).setTimeoutTask(id); - } - BukkitTask msgT = service.runTask(new MessageTask(service.getBukkitService(), plugin.getMessages(), - name, MessageKey.LOGIN_MESSAGE, interval)); - LimboCache.getInstance().getLimboPlayer(name).setMessageTask(msgT); + + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, true); + if (player.isInsideVehicle() && player.getVehicle() != null) { player.getVehicle().eject(); } + final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } player.setOp(false); - restoreSpeedEffect(); + restoreSpeedEffect(player); // Player is now logout... Time to fire event ! - Bukkit.getServer().getPluginManager().callEvent(new LogoutEvent(player)); - if (Settings.bungee) { - sendBungeeMessage(); + bukkitService.callEvent(new LogoutEvent(player)); + if (service.getProperty(HooksSettings.BUNGEECORD)) { + sendBungeeMessage(player); } service.send(player, MessageKey.LOGOUT_SUCCESS); ConsoleLogger.info(player.getName() + " logged out"); diff --git a/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java b/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java index c4e1a7f5..d31f305c 100644 --- a/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/AsynchronousQuit.java @@ -7,8 +7,9 @@ import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.datasource.CacheDataSource; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; +import fr.xephi.authme.process.SyncProcessManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.StringUtils; @@ -17,35 +18,42 @@ import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; -public class AsynchronousQuit implements Process { +import javax.inject.Inject; - private final AuthMe plugin; - private final DataSource database; - private final Player player; - private final String name; - private boolean isOp = false; - private boolean needToChange = false; - private final boolean isKick; - private final ProcessService service; +import static fr.xephi.authme.util.BukkitService.TICKS_PER_MINUTE; - public AsynchronousQuit(Player p, AuthMe plugin, DataSource database, boolean isKick, ProcessService service) { - this.player = p; - this.plugin = plugin; - this.database = database; - this.name = p.getName().toLowerCase(); - this.isKick = isKick; - this.service = service; - } +public class AsynchronousQuit implements AsynchronousProcess { - @Override - public void run() { + @Inject + private AuthMe plugin; + + @Inject + private DataSource database; + + @Inject + private ProcessService service; + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboCache limboCache; + + @Inject + private SyncProcessManager syncProcessManager; + + AsynchronousQuit() { } + + + public void processQuit(Player player, boolean isKick) { if (player == null || Utils.isUnrestricted(player)) { return; } + final String name = player.getName().toLowerCase(); String ip = Utils.getPlayerIp(player); - if (PlayerCache.getInstance().isAuthenticated(name)) { + if (playerCache.isAuthenticated(name)) { if (service.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)) { Location loc = player.getLocation(); PlayerAuth auth = PlayerAuth.builder() @@ -62,14 +70,17 @@ public class AsynchronousQuit implements Process { database.updateSession(auth); } - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); + boolean needToChange = false; + boolean isOp = false; + + LimboPlayer limbo = limboCache.getLimboPlayer(name); if (limbo != null) { if (!StringUtils.isEmpty(limbo.getGroup())) { Utils.addNormal(player, limbo.getGroup()); } needToChange = true; isOp = limbo.isOperator(); - LimboCache.getInstance().deleteLimboPlayer(name); + limboCache.deleteLimboPlayer(name); } if (Settings.isSessionsEnabled && !isKick) { if (Settings.getSessionTimeout != 0) { @@ -78,24 +89,24 @@ public class AsynchronousQuit implements Process { @Override public void run() { - postLogout(); + postLogout(name); } - }, Settings.getSessionTimeout * 20 * 60); + }, Settings.getSessionTimeout * TICKS_PER_MINUTE); plugin.sessions.put(name, task); } else { //plugin is disabled; we cannot schedule more tasks so run it directly here - postLogout(); + postLogout(name); } } } else { - PlayerCache.getInstance().removePlayer(name); + playerCache.removePlayer(name); database.setUnlogged(name); } if (plugin.isEnabled()) { - service.scheduleSyncDelayedTask(new ProcessSyncronousPlayerQuit(plugin, player, isOp, needToChange)); + syncProcessManager.processSyncPlayerQuit(player, isOp, needToChange); } // remove player from cache if (database instanceof CacheDataSource) { @@ -103,7 +114,7 @@ public class AsynchronousQuit implements Process { } } - private void postLogout() { + private void postLogout(String name) { PlayerCache.getInstance().removePlayer(name); database.setUnlogged(name); plugin.sessions.remove(name); 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 bf7ab967..c1382c2c 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -1,40 +1,12 @@ package fr.xephi.authme.process.quit; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.process.SynchronousProcess; import org.bukkit.entity.Player; -/** - */ -public class ProcessSyncronousPlayerQuit implements Runnable { - protected final AuthMe plugin; - protected final Player player; - protected final boolean isOp; - protected final boolean needToChange; +public class ProcessSyncronousPlayerQuit implements SynchronousProcess { - /** - * Constructor for ProcessSyncronousPlayerQuit. - * - * @param plugin AuthMe - * @param player Player - * @param isOp boolean - * @param needToChange boolean - */ - public ProcessSyncronousPlayerQuit(AuthMe plugin, Player player - , boolean isOp, boolean needToChange) { - this.plugin = plugin; - this.player = player; - this.isOp = isOp; - this.needToChange = needToChange; - } - - /** - * Method run. - * - * @see java.lang.Runnable#run() - */ - @Override - public void run() { + public void processSyncQuit(Player player, boolean isOp, boolean needToChange) { if (needToChange) { player.setOp(isOp); } 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 5cacb105..44f0fd0b 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -5,10 +5,12 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; +import fr.xephi.authme.process.SyncProcessManager; import fr.xephi.authme.security.HashAlgorithm; +import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.TwoFactor; import fr.xephi.authme.settings.Settings; @@ -18,40 +20,46 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; - +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.util.List; -/** - */ -public class AsyncRegister implements Process { +import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; - private final Player player; - private final String name; - private final String password; - private final String ip; - private final String email; - private final AuthMe plugin; - private final DataSource database; - private final PlayerCache playerCache; - private final ProcessService service; +public class AsyncRegister implements AsynchronousProcess { - public AsyncRegister(Player player, String password, String email, AuthMe plugin, DataSource data, - PlayerCache playerCache, ProcessService service) { - this.player = player; - this.password = password; - this.name = player.getName().toLowerCase(); - this.email = email; - this.plugin = plugin; - this.database = data; - this.ip = Utils.getPlayerIp(player); - this.playerCache = playerCache; - this.service = service; - } + @Inject + private AuthMe plugin; - private boolean preRegisterCheck() { + @Inject + private DataSource database; + + @Inject + private PlayerCache playerCache; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private ProcessService service; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private ValidationService validationService; + + AsyncRegister() { } + + private boolean preRegisterCheck(Player player, String password) { + final String name = player.getName().toLowerCase(); if (playerCache.isAuthenticated(name)) { service.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return false; @@ -62,9 +70,9 @@ public class AsyncRegister implements Process { //check the password safety only if it's not a automatically generated password if (service.getProperty(SecuritySettings.PASSWORD_HASH) != HashAlgorithm.TWO_FACTOR) { - MessageKey passwordError = service.validatePassword(password, player.getName()); - if (passwordError != null) { - service.send(player, passwordError); + ValidationResult passwordValidation = validationService.validatePassword(password, player.getName()); + if (passwordValidation.hasError()) { + service.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs()); return false; } } @@ -76,10 +84,11 @@ public class AsyncRegister implements Process { } final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP); + final String ip = Utils.getPlayerIp(player); if (maxRegPerIp > 0 && !"127.0.0.1".equalsIgnoreCase(ip) && !"localhost".equalsIgnoreCase(ip) - && !plugin.getPermissionsManager().hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)) { + && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { List otherAccounts = database.getAllAuthsByIp(ip); if (otherAccounts.size() >= maxRegPerIp) { service.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerIp), @@ -90,21 +99,20 @@ public class AsyncRegister implements Process { return true; } - @Override - public void run() { - if (preRegisterCheck()) { + public void register(Player player, String password, String email, boolean autoLogin) { + if (preRegisterCheck(player, password)) { if (!StringUtils.isEmpty(email)) { - emailRegister(); + emailRegister(player, password, email); } else { - passwordRegister(); + passwordRegister(player, password, autoLogin); } } } - private void emailRegister() { + private void emailRegister(Player player, String password, String email) { + final String name = player.getName().toLowerCase(); final int maxRegPerEmail = service.getProperty(EmailSettings.MAX_REG_PER_EMAIL); - if (maxRegPerEmail > 0 - && !plugin.getPermissionsManager().hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)) { + if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { int otherAccounts = database.countAuthsByEmail(email); if (otherAccounts >= maxRegPerEmail) { service.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerEmail), @@ -113,7 +121,8 @@ public class AsyncRegister implements Process { } } - final HashedPassword hashedPassword = service.computeHash(password, name); + final HashedPassword hashedPassword = passwordSecurity.computeHash(password, name); + final String ip = Utils.getPlayerIp(player); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) @@ -130,13 +139,13 @@ public class AsyncRegister implements Process { database.updateEmail(auth); database.updateSession(auth); plugin.mail.main(auth, password); - ProcessSyncEmailRegister sync = new ProcessSyncEmailRegister(player, service); - service.scheduleSyncDelayedTask(sync); - + syncProcessManager.processSyncEmailRegister(player); } - private void passwordRegister() { - final HashedPassword hashedPassword = service.computeHash(password, name); + private void passwordRegister(Player player, String password, boolean autoLogin) { + final String name = player.getName().toLowerCase(); + final String ip = Utils.getPlayerIp(player); + final HashedPassword hashedPassword = passwordSecurity.computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) @@ -150,15 +159,13 @@ public class AsyncRegister implements Process { return; } - if (!Settings.forceRegLogin) { + if (!Settings.forceRegLogin && autoLogin) { //PlayerCache.getInstance().addPlayer(auth); //database.setLogged(name); // TODO: check this... plugin.getManagement().performLogin(player, "dontneed", true); } - - ProcessSyncPasswordRegister sync = new ProcessSyncPasswordRegister(player, plugin, service); - service.scheduleSyncDelayedTask(sync); + syncProcessManager.processSyncPasswordRegister(player); //give the user the secret code to setup their app code generation if (service.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { 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 461857aa..41d8fe97 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -1,60 +1,39 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.ProcessService; +import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.task.MessageTask; -import fr.xephi.authme.task.TimeoutTask; +import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.Utils; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; -/** - */ -public class ProcessSyncEmailRegister implements Process { +import javax.inject.Inject; - private final Player player; - private final String name; - private final ProcessService service; - /** - * Constructor for ProcessSyncEmailRegister. - * - * @param player The player to process an email registration for - * @param service The process service - */ - public ProcessSyncEmailRegister(Player player, ProcessService service) { - this.player = player; - this.name = player.getName().toLowerCase(); - this.service = service; - } +public class ProcessSyncEmailRegister implements SynchronousProcess { - @Override - public void run() { - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); + @Inject + private ProcessService service; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + ProcessSyncEmailRegister() { } + + + public void processEmailRegister(Player player) { + final String name = player.getName().toLowerCase(); if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.REGISTERED); + service.setGroup(player, AuthGroupType.REGISTERED); } service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED); - int time = service.getProperty(RestrictionSettings.TIMEOUT) * 20; - int msgInterval = service.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - if (limbo != null) { - if (time != 0) { - BukkitTask id = service.runTaskLater(new TimeoutTask(service.getAuthMe(), name, player), time); - limbo.setTimeoutTask(id); - } - BukkitTask messageTask = service.runTask(new MessageTask( - service.getBukkitService(), name, service.retrieveMessage(MessageKey.LOGIN_MESSAGE), msgInterval)); - limbo.setMessageTask(messageTask); - } + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, true); player.saveData(); if (!service.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { 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 b9505143..16f1ad36 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -9,51 +9,58 @@ import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.events.LoginEvent; import fr.xephi.authme.events.RestoreInventoryEvent; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.ProcessService; +import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.task.MessageTask; -import fr.xephi.authme.task.TimeoutTask; +import fr.xephi.authme.task.LimboPlayerTaskManager; +import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitTask; + +import javax.inject.Inject; import static fr.xephi.authme.settings.properties.RestrictionSettings.HIDE_TABLIST_BEFORE_LOGIN; -import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; /** */ -public class ProcessSyncPasswordRegister implements Process { +public class ProcessSyncPasswordRegister implements SynchronousProcess { - private final Player player; - private final String name; - private final AuthMe plugin; - private final ProcessService service; + @Inject + private AuthMe plugin; - public ProcessSyncPasswordRegister(Player player, AuthMe plugin, ProcessService service) { - this.player = player; - this.name = player.getName().toLowerCase(); - this.plugin = plugin; - this.service = service; - } + @Inject + private ProcessService service; - private void sendBungeeMessage() { + @Inject + private BukkitService bukkitService; + + @Inject + private LimboCache limboCache; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + ProcessSyncPasswordRegister() { } + + + private void sendBungeeMessage(Player player) { ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("Forward"); out.writeUTF("ALL"); out.writeUTF("AuthMe"); - out.writeUTF("register;" + name); + out.writeUTF("register;" + player.getName()); player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); } - private void forceCommands() { + private void forceCommands(Player player) { for (String command : service.getProperty(RegistrationSettings.FORCE_REGISTER_COMMANDS)) { player.performCommand(command.replace("%p", player.getName())); } @@ -69,27 +76,21 @@ public class ProcessSyncPasswordRegister implements Process { * @param player the player */ private void requestLogin(Player player) { + final String name = player.getName().toLowerCase(); Utils.teleportToSpawn(player); - LimboCache cache = LimboCache.getInstance(); - cache.updateLimboPlayer(player); - int delay = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - int interval = service.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - BukkitTask task; - if (delay != 0) { - task = service.runTaskLater(new TimeoutTask(service.getAuthMe(), name, player), delay); - cache.getLimboPlayer(name).setTimeoutTask(task); - } - task = service.runTask(new MessageTask(service.getBukkitService(), plugin.getMessages(), - name, MessageKey.LOGIN_MESSAGE, interval)); - cache.getLimboPlayer(name).setMessageTask(task); + + limboCache.updateLimboPlayer(player); + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, true); + if (player.isInsideVehicle() && player.getVehicle() != null) { player.getVehicle().eject(); } } - @Override - public void run() { - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); + public void processPasswordRegister(Player player) { + final String name = player.getName().toLowerCase(); + LimboPlayer limbo = limboCache.getLimboPlayer(name); if (limbo != null) { if (service.getProperty(RestrictionSettings.HIDE_TABLIST_BEFORE_LOGIN) && plugin.tablistHider != null) { plugin.tablistHider.sendTablist(player); @@ -99,17 +100,17 @@ public class ProcessSyncPasswordRegister implements Process { if (service.getProperty(HIDE_TABLIST_BEFORE_LOGIN) && plugin.inventoryProtector != null) { RestoreInventoryEvent event = new RestoreInventoryEvent(player); - service.callEvent(event); + bukkitService.callEvent(event); if (!event.isCancelled()) { plugin.inventoryProtector.sendInventoryPacket(player); } } - LimboCache.getInstance().deleteLimboPlayer(name); + limboCache.deleteLimboPlayer(name); } if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.REGISTERED); + service.setGroup(player, AuthGroupType.REGISTERED); } service.send(player, MessageKey.REGISTER_SUCCESS); @@ -123,7 +124,7 @@ public class ProcessSyncPasswordRegister implements Process { } // The LoginEvent now fires (as intended) after everything is processed - plugin.getServer().getPluginManager().callEvent(new LoginEvent(player)); + bukkitService.callEvent(new LoginEvent(player)); player.saveData(); if (!service.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)) { @@ -137,7 +138,7 @@ public class ProcessSyncPasswordRegister implements Process { } // Register is now finished; we can force all commands - forceCommands(); + forceCommands(player); // Request login after registration if (service.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)) { @@ -146,13 +147,13 @@ public class ProcessSyncPasswordRegister implements Process { } if (service.getProperty(HooksSettings.BUNGEECORD)) { - sendBungeeMessage(); + sendBungeeMessage(player); } - sendTo(); + sendTo(player); } - private void sendTo() { + private void sendTo(Player player) { if (!service.getProperty(HooksSettings.BUNGEECORD_SERVER).isEmpty()) { ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("Connect"); 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 cd3f41a2..b9b87252 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -1,99 +1,84 @@ package fr.xephi.authme.process.unregister; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.process.Process; +import fr.xephi.authme.permission.AuthGroupType; +import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.ProcessService; +import fr.xephi.authme.security.PasswordSecurity; 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 fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.Utils.GroupType; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitTask; -public class AsynchronousUnregister implements Process { +import javax.inject.Inject; - private final Player player; - private final String name; - private final String password; - private final boolean force; - private final AuthMe plugin; - private final JsonCache playerCache; - private final ProcessService service; +import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; - /** - * Constructor. - * - * @param player The player to perform the action for - * @param password The password - * @param force True to bypass password validation - * @param plugin The plugin instance - * @param service The process service - */ - public AsynchronousUnregister(Player player, String password, boolean force, AuthMe plugin, - ProcessService service) { - this.player = player; - this.name = player.getName().toLowerCase(); - this.password = password; - this.force = force; - this.plugin = plugin; - this.playerCache = new JsonCache(); - this.service = service; - } +public class AsynchronousUnregister implements AsynchronousProcess { - @Override - public void run() { - PlayerAuth cachedAuth = PlayerCache.getInstance().getAuth(name); - if (force || plugin.getPasswordSecurity().comparePassword( - password, cachedAuth.getPassword(), player.getName())) { - if (!service.getDataSource().removeAuth(name)) { + @Inject + private DataSource dataSource; + + @Inject + private ProcessService service; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboCache limboCache; + + @Inject + private LimboPlayerTaskManager limboPlayerTaskManager; + + AsynchronousUnregister() { } + + + public void unregister(Player player, String password, boolean force) { + final String name = player.getName().toLowerCase(); + PlayerAuth cachedAuth = playerCache.getAuth(name); + if (force || passwordSecurity.comparePassword(password, cachedAuth.getPassword(), player.getName())) { + if (!dataSource.removeAuth(name)) { service.send(player, MessageKey.ERROR); return; } - int timeOut = service.getProperty(RestrictionSettings.TIMEOUT) * 20; - if (Settings.isForcedRegistrationEnabled) { + + if (service.getProperty(RegistrationSettings.FORCE)) { Utils.teleportToSpawn(player); player.saveData(); - PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); + playerCache.removePlayer(player.getName().toLowerCase()); if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, GroupType.UNREGISTERED); + service.setGroup(player, AuthGroupType.UNREGISTERED); } - LimboCache.getInstance().addLimboPlayer(player); - LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); - int interval = service.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - if (timeOut != 0) { - BukkitTask id = service.runTaskLater(new TimeoutTask(plugin, name, player), timeOut); - limboPlayer.setTimeoutTask(id); - } - limboPlayer.setMessageTask(service.runTask(new MessageTask(service.getBukkitService(), - plugin.getMessages(), name, MessageKey.REGISTER_MESSAGE, interval))); + limboCache.addLimboPlayer(player); + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, false); + service.send(player, MessageKey.UNREGISTERED_SUCCESS); ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - return; + return; // TODO ljacqu 20160612: Why return here? No blind effect? Player not removed from PlayerCache? } if (!Settings.unRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.UNREGISTERED); - } - PlayerCache.getInstance().removePlayer(name); - // check if Player cache File Exist and delete it, preventing - // duplication of items - if (playerCache.doesCacheExist(player)) { - playerCache.removeCache(player); + service.setGroup(player, AuthGroupType.UNREGISTERED); } + playerCache.removePlayer(name); + // Apply blind effect + int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } service.send(player, MessageKey.UNREGISTERED_SUCCESS); ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 94a8abeb..6e6eac83 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -2,32 +2,45 @@ package fr.xephi.authme.security; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; +import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.plugin.PluginManager; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; +import javax.annotation.PostConstruct; +import javax.inject.Inject; /** * Manager class for password-related operations. */ -public class PasswordSecurity { +public class PasswordSecurity implements Reloadable { + + @Inject + private NewSetting settings; + + @Inject + private DataSource dataSource; + + @Inject + private PluginManager pluginManager; + + @Inject + private AuthMeServiceInitializer initializer; - private final NewSetting settings; private HashAlgorithm algorithm; private boolean supportOldAlgorithm; - private final DataSource dataSource; - private final PluginManager pluginManager; - public PasswordSecurity(DataSource dataSource, NewSetting settings, PluginManager pluginManager) { - this.settings = settings; + /** + * Load or reload the configuration. + */ + @PostConstruct + @Override + public void reload() { this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH); - this.dataSource = dataSource; - this.pluginManager = pluginManager; } /** @@ -73,14 +86,6 @@ public class PasswordSecurity { || supportOldAlgorithm && compareWithAllEncryptionMethods(password, hashedPassword, playerLowerCase); } - /** - * Reload the configuration. - */ - public void reload() { - this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); - this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH); - } - /** * Compare the given hash with all available encryption methods to support * the migration to a new encryption method. Upon a successful match, the password @@ -95,7 +100,7 @@ public class PasswordSecurity { private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, String playerName) { for (HashAlgorithm algorithm : HashAlgorithm.values()) { if (!HashAlgorithm.CUSTOM.equals(algorithm)) { - EncryptionMethod method = initializeEncryptionMethod(algorithm, settings); + EncryptionMethod method = initializeEncryptionMethod(algorithm); if (methodMatches(method, password, hashedPassword, playerName)) { hashPasswordForNewAlgorithm(password, playerName); return true; @@ -133,7 +138,7 @@ public class PasswordSecurity { * @return The encryption method */ private EncryptionMethod initializeEncryptionMethodWithEvent(HashAlgorithm algorithm, String playerName) { - EncryptionMethod method = initializeEncryptionMethod(algorithm, settings); + EncryptionMethod method = initializeEncryptionMethod(algorithm); PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); pluginManager.callEvent(event); return event.getMethod(); @@ -143,30 +148,14 @@ public class PasswordSecurity { * Initialize the encryption method associated with the given hash algorithm. * * @param algorithm The algorithm to retrieve the encryption method for - * @param settings The settings instance to pass to the constructor if required * * @return The associated encryption method, or null if CUSTOM / deprecated */ - public static EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, - NewSetting settings) { - try { - if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) { - return null; - } - Constructor constructor = algorithm.getClazz().getConstructors()[0]; - Class[] parameters = constructor.getParameterTypes(); - if (parameters.length == 0) { - return (EncryptionMethod) constructor.newInstance(); - } else if (parameters.length == 1 && parameters[0] == NewSetting.class) { - return (EncryptionMethod) constructor.newInstance(settings); - } else { - throw new UnsupportedOperationException("Did not find default constructor or constructor with settings " - + "parameter in class " + algorithm.getClazz().getSimpleName()); - } - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new UnsupportedOperationException("Constructor for '" + algorithm.getClazz().getSimpleName() - + "' could not be invoked. (Is there no default constructor?)", e); + private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm) { + if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) { + return null; } + return initializer.newInstance(algorithm.getClazz()); } private void hashPasswordForNewAlgorithm(String password, String playerName) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index a114adc0..cda26091 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -9,15 +9,17 @@ import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.util.StringUtils; +import javax.inject.Inject; @Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8 -@HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds +@HasSalt(value = SaltType.TEXT) // length depends on the bcryptLog2Rounds setting public class BCRYPT implements EncryptionMethod { private final int bCryptLog2Rounds; + @Inject public BCRYPT(NewSetting settings) { - this.bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); + bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); } @Override @@ -50,4 +52,5 @@ public class BCRYPT implements EncryptionMethod { public boolean hasSeparateSalt() { return false; } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java b/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java index 194ea7b6..6b82305c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCryptService.java @@ -380,9 +380,9 @@ public class BCryptService { * encoding scheme. Note that this is *not* compatible with * the standard MIME-base64 encoding. * - * @param d the byte array to encode - * @param len the number of bytes to encode - * @return base64-encoded string + * @param d the byte array to encode + * @param len the number of bytes to encode + * @return base64-encoded string * @exception IllegalArgumentException if the length is invalid */ private static String encode_base64(byte d[], int len) @@ -421,8 +421,8 @@ public class BCryptService { /** * Look up the 3 bits base64-encoded by the specified character, * range-checking againt conversion table - * @param x the base64-encoded value - * @return the decoded value of x + * @param x the base64-encoded value + * @return the decoded value of x */ private static byte char64(char x) { if ((int)x < 0 || (int)x > index_64.length) @@ -434,9 +434,9 @@ public class BCryptService { * Decode a string encoded using bcrypt's base64 scheme to a * byte array. Note that this is *not* compatible with * the standard MIME-base64 encoding. - * @param s the string to decode - * @param maxolen the maximum number of bytes to decode - * @return an array containing the decoded bytes + * @param s the string to decode + * @param maxolen the maximum number of bytes to decode + * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid */ private static byte[] decode_base64(String s, int maxolen) @@ -483,8 +483,8 @@ public class BCryptService { /** * Blowfish encipher a single 64-bit block encoded as * two 32-bit halves - * @param lr an array containing the two 32-bit half blocks - * @param off the position in the array of the blocks + * @param lr an array containing the two 32-bit half blocks + * @param off the position in the array of the blocks */ private void encipher(int lr[], int off) { int i, n, l = lr[off], r = lr[off + 1]; @@ -511,10 +511,10 @@ public class BCryptService { /** * Cycically extract a word of key material - * @param data the string to extract the data from - * @param offp a "pointer" (as a one-entry array) to the + * @param data the string to extract the data from + * @param offp a "pointer" (as a one-entry array) to the * current offset into data - * @return the next word of material from data + * @return the next word of material from data */ private static int streamtoword(byte data[], int offp[]) { int i; @@ -540,7 +540,7 @@ public class BCryptService { /** * Key the Blowfish cipher - * @param key an array containing the key + * @param key an array containing the key */ private void key(byte key[]) { int i; @@ -568,8 +568,8 @@ public class BCryptService { * Perform the "enhanced key schedule" step described by * Provos and Mazieres in "A Future-Adaptable Password Scheme" * http://www.openbsd.org/papers/bcrypt-paper.ps - * @param data salt information - * @param key password information + * @param data salt information + * @param key password information */ private void ekskey(byte data[], byte key[]) { int i; @@ -600,12 +600,12 @@ public class BCryptService { /** * Perform the central password hashing step in the * bcrypt scheme - * @param password the password to hash - * @param salt the binary salt to hash with the password - * @param log_rounds the binary logarithm of the number + * @param password the password to hash + * @param salt the binary salt to hash with the password + * @param log_rounds the binary logarithm of the number * of rounds of hashing to apply * @param cdata the plaintext to encrypt - * @return an array containing the binary hashed password + * @return an array containing the binary hashed password */ public byte[] crypt_raw(byte password[], byte salt[], int log_rounds, int cdata[]) { @@ -643,10 +643,10 @@ public class BCryptService { /** * Hash a password using the OpenBSD bcrypt scheme - * @param password the password to hash - * @param salt the salt to hash with (perhaps generated + * @param password the password to hash + * @param salt the salt to hash with (perhaps generated * using BCrypt.gensalt) - * @return the hashed password + * @return the hashed password */ public static String hashpw(String password, String salt) { BCryptService B; @@ -706,11 +706,11 @@ public class BCryptService { /** * Generate a salt for use with the BCrypt.hashpw() method - * @param log_rounds the log2 of the number of rounds of + * @param log_rounds the log2 of the number of rounds of * hashing to apply - the work factor therefore increases as * 2**log_rounds. - * @param random an instance of SecureRandom to use - * @return an encoded salt value + * @param random an instance of SecureRandom to use + * @return an encoded salt value */ public static String gensalt(int log_rounds, SecureRandom random) { StringBuilder rs = new StringBuilder(); @@ -733,10 +733,10 @@ public class BCryptService { /** * Generate a salt for use with the BCrypt.hashpw() method - * @param log_rounds the log2 of the number of rounds of + * @param log_rounds the log2 of the number of rounds of * hashing to apply - the work factor therefore increases as * 2**log_rounds. - * @return an encoded salt value + * @return an encoded salt value */ public static String gensalt(int log_rounds) { return gensalt(log_rounds, new SecureRandom()); @@ -746,7 +746,7 @@ public class BCryptService { * Generate a salt for use with the BCrypt.hashpw() method, * selecting a reasonable default for the number of hashing * rounds to apply - * @return an encoded salt value + * @return an encoded salt value */ public static String gensalt() { return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); @@ -755,9 +755,9 @@ public class BCryptService { /** * Check that a plaintext password matches a previously hashed * one - * @param plaintext the plaintext password to verify - * @param hashed the previously-hashed password - * @return true if the passwords match, false otherwise + * @param plaintext the plaintext password to verify + * @param hashed the previously-hashed password + * @return true if the passwords match, false otherwise */ public static boolean checkpw(String plaintext, String hashed) { byte hashed_bytes[]; diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index c07db986..074143fd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -1,7 +1,3 @@ -/* - * To change this template, choose Tools | Templates and open the template in - * the editor. - */ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; @@ -24,7 +20,7 @@ public class PHPBB extends HexSaltedMethod { byte[] hash = md5er.digest(bytes); return bytes2hex(hash); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + throw new UnsupportedOperationException(e); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 27066f7a..a96cc0d5 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -8,14 +8,17 @@ import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.SecuritySettings; +import javax.inject.Inject; + 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 Settings.saltLength +@HasSalt(value = SaltType.TEXT) // length defined by the doubleMd5SaltLength setting public class SALTED2MD5 extends SeparateSaltMethod { private final int saltLength; + @Inject public SALTED2MD5(NewSetting settings) { saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH); } @@ -30,4 +33,5 @@ public class SALTED2MD5 extends SeparateSaltMethod { return RandomString.generateHex(saltLength); } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 832afa2a..175efc3f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -4,6 +4,7 @@ import fr.xephi.authme.security.HashUtils; public class SMF extends UsernameSaltMethod { + @Override public HashedPassword computeHash(String password, String name) { return new HashedPassword(HashUtils.sha1(name.toLowerCase() + password)); } diff --git a/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java b/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java index daadcae8..34bf8aac 100644 --- a/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java +++ b/src/main/java/fr/xephi/authme/security/pbkdf2/BinTools.java @@ -1,71 +1,62 @@ package fr.xephi.authme.security.pbkdf2; +/* + * Free auxiliary functions. Copyright 2007, 2014, Matthias Gärtner + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + /** - *

- * Free auxiliary functions. Copyright (c) 2007 Matthias Gärtner - *

- *

- * This library is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 2.1 of the License, or (at your option) - * any later version. - *

- *

- * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - *

- *

- * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - *

- *

- * For Details, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. - *

+ * Free auxiliary functions * * @author Matthias Gärtner - * @version 1.0 */ public class BinTools { - - private static final String HEX_CHARS = "0123456789ABCDEF"; - - private BinTools() { - } + public static final String hex = "0123456789ABCDEF"; /** * Simple binary-to-hexadecimal conversion. * - * @param b Input bytes. May be null. - * + * @param b + * Input bytes. May be null. * @return Hexadecimal representation of b. Uppercase A-F, two characters - * per byte. Empty string on null input. + * per byte. Empty string on null input. */ public static String bin2hex(final byte[] b) { if (b == null) { return ""; } - StringBuffer stringBuffer = new StringBuffer(2 * b.length); - for (byte aB : b) { - int v = (256 + aB) % 256; - stringBuffer.append(HEX_CHARS.charAt((v / 16) & 15)); - stringBuffer.append(HEX_CHARS.charAt((v % 16) & 15)); + StringBuffer sb = new StringBuffer(2 * b.length); + for (int i = 0; i < b.length; i++) { + int v = (256 + b[i]) % 256; + sb.append(hex.charAt((v / 16) & 15)); + sb.append(hex.charAt((v % 16) & 15)); } - return stringBuffer.toString(); + return sb.toString(); } /** * Convert hex string to array of bytes. * - * @param s String containing hexadecimal digits. May be null. - * On odd length leading zero will be assumed. - * + * @param s + * String containing hexadecimal digits. May be null + * . On odd length leading zero will be assumed. * @return Array on bytes, non-null. - * @throws IllegalArgumentException when string contains non-hex character + * @throws IllegalArgumentException + * when string contains non-hex character */ public static byte[] hex2bin(final String s) { String m = s; @@ -88,10 +79,11 @@ public class BinTools { /** * Convert hex digit to numerical value. * - * @param c 0-9, a-f, A-F allowed. - * + * @param c + * 0-9, a-f, A-F allowd. * @return 0-15 - * @throws IllegalArgumentException on non-hex character + * @throws IllegalArgumentException + * on non-hex character */ public static int hex2bin(char c) { if (c >= '0' && c <= '9') { @@ -106,9 +98,7 @@ public class BinTools { throw new IllegalArgumentException("Input string may only contain hex digits, but found '" + c + "'"); } - // Note ljacqu 20160313: This appears to be a test method that was present in the third-party source. - // We can keep it for troubleshooting in the future. - private static void testUtils() { + public static void main(String[] args) { byte b[] = new byte[256]; byte bb = 0; for (int i = 0; i < 256; i++) { diff --git a/src/main/java/fr/xephi/authme/settings/NewSetting.java b/src/main/java/fr/xephi/authme/settings/NewSetting.java index 89717a8c..90719daa 100644 --- a/src/main/java/fr/xephi/authme/settings/NewSetting.java +++ b/src/main/java/fr/xephi/authme/settings/NewSetting.java @@ -1,6 +1,8 @@ package fr.xephi.authme.settings; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; import com.google.common.io.Files; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.domain.Property; @@ -121,10 +123,20 @@ public class NewSetting { return "/messages/messages_en.yml"; } + /** + * Return the text to use in email registrations. + * + * @return The email message + */ public String getEmailMessage() { return emailMessage; } + /** + * Return the lines to output after an in-game registration. + * + * @return The welcome message + */ public List getWelcomeMessage() { return welcomeMessage; } @@ -206,7 +218,7 @@ public class NewSetting { private String toYaml(Property property, int indent, Yaml simpleYaml, Yaml singleQuoteYaml) { String representation = property.toYaml(configuration, simpleYaml, singleQuoteYaml); - return join("\n" + indent(indent), representation.split("\\n")); + return Joiner.on("\n" + indent(indent)).join(representation.split("\\n")); } private File buildMessagesFile() { @@ -251,7 +263,7 @@ public class NewSetting { final Charset charset = Charset.forName("UTF-8"); if (copyFileFromResource(emailFile, "email.html")) { try { - return StringUtils.join("\n", Files.readLines(emailFile, charset)); + return Files.toString(emailFile, charset); } catch (IOException e) { ConsoleLogger.logException("Failed to read file '" + emailFile.getPath() + "':", e); } @@ -269,23 +281,9 @@ public class NewSetting { return new Yaml(options); } - private static String join(String delimiter, String[] items) { - StringBuilder sb = new StringBuilder(); - String delim = ""; - for (String item : items) { - sb.append(delim).append(item); - delim = delimiter; - } - return sb.toString(); - } - private static String indent(int level) { // We use an indentation of 4 spaces - StringBuilder sb = new StringBuilder(level * 4); - for (int i = 0; i < level; ++i) { - sb.append(" "); - } - return sb.toString(); + return Strings.repeat(" ", level * 4); } } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index 944f675d..23580155 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -1,47 +1,41 @@ package fr.xephi.authme.settings; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.settings.domain.Property; -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.settings.properties.SecuritySettings; import org.bukkit.configuration.file.FileConfiguration; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.regex.Pattern; /** * Old settings manager. See {@link NewSetting} for the new manager. */ +@Deprecated public final class Settings { - public static List allowCommands; public static List getUnrestrictedName; - public static List getForcedWorlds; - public static List countries; - public static List countriesBlacklist; - public static HashAlgorithm getPasswordHash; - public static Pattern nickPattern; - public static boolean isPermissionCheckEnabled, - isForcedRegistrationEnabled, isTeleportToSpawnEnabled, - isSessionsEnabled, isAllowRestrictedIp, - isForceSingleSessionEnabled, isForceSpawnLocOnJoinEnabled, - isSaveQuitLocationEnabled, protectInventoryBeforeLogInEnabled, - isStopEnabled, reloadSupport, rakamakUseIp, - removePassword, multiverse, bungee, - enableProtection, forceRegLogin, noTeleport, - allowAllCommandsIfRegIsOptional, isRemoveSpeedEnabled; - public static String getNickRegex, getUnloggedinGroup, - unRegisteredGroup, backupWindowsPath, getRegisteredGroup, - rakamakUsers, rakamakUsersIp, defaultWorld, crazyloginFileName; - public static int getSessionTimeout, getMaxNickLength, getMinNickLength, - getNonActivatedGroup, maxLoginTry, captchaLength, getMaxLoginPerIp; - protected static FileConfiguration configFile; + public static boolean isPermissionCheckEnabled; + public static boolean isTeleportToSpawnEnabled; + public static boolean isSessionsEnabled; + public static boolean isAllowRestrictedIp; + public static boolean isSaveQuitLocationEnabled; + public static boolean protectInventoryBeforeLogInEnabled; + public static boolean isStopEnabled; + public static boolean reloadSupport; + public static boolean forceRegLogin; + public static boolean noTeleport; + public static boolean isRemoveSpeedEnabled; + public static String getUnloggedinGroup; + public static String unRegisteredGroup; + public static String getRegisteredGroup; + public static String defaultWorld; + public static String crazyloginFileName; + public static int getSessionTimeout; + public static int getNonActivatedGroup; + private static FileConfiguration configFile; /** * Constructor for Settings. @@ -55,60 +49,22 @@ public final class Settings { private static void loadVariables() { isPermissionCheckEnabled = load(PluginSettings.ENABLE_PERMISSION_CHECK); - isForcedRegistrationEnabled = load(RegistrationSettings.FORCE); isTeleportToSpawnEnabled = load(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN); isSessionsEnabled = load(PluginSettings.SESSIONS_ENABLED); getSessionTimeout = configFile.getInt("settings.sessions.timeout", 10); - getMaxNickLength = configFile.getInt("settings.restrictions.maxNicknameLength", 20); - getMinNickLength = configFile.getInt("settings.restrictions.minNicknameLength", 3); - getNickRegex = configFile.getString("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_?]*"); - nickPattern = Pattern.compile(getNickRegex); isAllowRestrictedIp = load(RestrictionSettings.ENABLE_RESTRICTED_USERS); isRemoveSpeedEnabled = load(RestrictionSettings.REMOVE_SPEED); - isForceSingleSessionEnabled = load(RestrictionSettings.FORCE_SINGLE_SESSION); - isForceSpawnLocOnJoinEnabled = load(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN); - isSaveQuitLocationEnabled = configFile.getBoolean("settings.restrictions.SaveQuitLocation", false); - getPasswordHash = load(SecuritySettings.PASSWORD_HASH); + isSaveQuitLocationEnabled = load(RestrictionSettings.SAVE_QUIT_LOCATION); getUnloggedinGroup = load(SecuritySettings.UNLOGGEDIN_GROUP); getNonActivatedGroup = configFile.getInt("ExternalBoardOptions.nonActivedUserGroup", -1); unRegisteredGroup = configFile.getString("GroupOptions.UnregisteredPlayerGroup", ""); - - getUnrestrictedName = new ArrayList<>(); - for (String name : configFile.getStringList("settings.unrestrictions.UnrestrictedName")) { - getUnrestrictedName.add(name.toLowerCase()); - } - + getUnrestrictedName = load(RestrictionSettings.UNRESTRICTED_NAMES); getRegisteredGroup = configFile.getString("GroupOptions.RegisteredPlayerGroup", ""); protectInventoryBeforeLogInEnabled = load(RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN); - backupWindowsPath = configFile.getString("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\"); isStopEnabled = configFile.getBoolean("Security.SQLProblem.stopServer", true); reloadSupport = configFile.getBoolean("Security.ReloadCommand.useReloadCommandSupport", true); - - allowAllCommandsIfRegIsOptional = load(RestrictionSettings.ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL); - allowCommands = new ArrayList<>(); - allowCommands.addAll(Arrays.asList("/login", "/l", "/register", "/reg", "/email", "/captcha")); - for (String cmd : configFile.getStringList("settings.restrictions.allowCommands")) { - cmd = cmd.toLowerCase(); - if (!allowCommands.contains(cmd)) { - allowCommands.add(cmd); - } - } - - rakamakUsers = configFile.getString("Converter.Rakamak.fileName", "users.rak"); - rakamakUsersIp = configFile.getString("Converter.Rakamak.ipFileName", "UsersIp.rak"); - rakamakUseIp = configFile.getBoolean("Converter.Rakamak.useIp", false); - removePassword = configFile.getBoolean("Security.console.removePassword", true); - maxLoginTry = configFile.getInt("Security.captcha.maxLoginTry", 5); - captchaLength = configFile.getInt("Security.captcha.captchaLength", 5); - multiverse = load(HooksSettings.MULTIVERSE); - bungee = load(HooksSettings.BUNGEECORD); - getForcedWorlds = configFile.getStringList("settings.restrictions.ForceSpawnOnTheseWorlds"); defaultWorld = configFile.getString("Purge.defaultWorld", "world"); - enableProtection = configFile.getBoolean("Protection.enableProtection", false); - countries = configFile.getStringList("Protection.countries"); - countriesBlacklist = configFile.getStringList("Protection.countriesBlacklist"); forceRegLogin = load(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER); - getMaxLoginPerIp = load(RestrictionSettings.MAX_LOGIN_PER_IP); noTeleport = load(RestrictionSettings.NO_TELEPORT); crazyloginFileName = configFile.getString("Converter.CrazyLogin.fileName", "accounts.db"); } diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index 0982713c..fdafea6f 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -8,11 +8,14 @@ import org.bukkit.configuration.file.FileConfiguration; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.List; import static fr.xephi.authme.settings.properties.RegistrationSettings.DELAY_JOIN_MESSAGE; import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_JOIN_MESSAGE; import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_LEAVE_MESSAGE; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS; +import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN; +import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAWN_ON_WORLDS; /** * Service for verifying that the configuration is up-to-date. @@ -44,7 +47,8 @@ public class SettingsMigrationService { // ensures that all migrations will be performed return changes | performMailTextToFileMigration(configuration, pluginFolder) - | migrateJoinLeaveMessages(configuration); + | migrateJoinLeaveMessages(configuration) + | migrateForceSpawnSettings(configuration); } public boolean containsAllSettings(FileConfiguration configuration, PropertyMap propertyMap) { @@ -59,7 +63,8 @@ public class SettingsMigrationService { private static boolean hasDeprecatedProperties(FileConfiguration configuration) { String[] deprecatedProperties = { "Converter.Rakamak.newPasswordHash", "Hooks.chestshop", "Hooks.legacyChestshop", "Hooks.notifications", - "Passpartu", "Performances", "settings.restrictions.enablePasswordVerifier", "Xenoforo.predefinedSalt", "VeryGames"}; + "Passpartu", "Performances", "settings.restrictions.enablePasswordVerifier", "Xenoforo.predefinedSalt", + "VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional"}; for (String deprecatedPath : deprecatedProperties) { if (configuration.contains(deprecatedPath)) { return true; @@ -109,14 +114,50 @@ public class SettingsMigrationService { * @return True if the configuration has changed, false otherwise */ private static boolean migrateJoinLeaveMessages(FileConfiguration configuration) { - final String oldDelayJoinPath = "settings.delayJoinLeaveMessages"; - if (configuration.contains(oldDelayJoinPath)) { - ConsoleLogger.info("Detected deprecated property " + oldDelayJoinPath); + Property oldDelayJoinProperty = Property.newProperty("settings.delayJoinLeaveMessages", false); + boolean hasMigrated = moveProperty(oldDelayJoinProperty, DELAY_JOIN_MESSAGE, configuration); + + if (hasMigrated) { ConsoleLogger.info(String.format("Note that we now also have the settings %s and %s", REMOVE_JOIN_MESSAGE.getPath(), REMOVE_LEAVE_MESSAGE.getPath())); - if (!configuration.contains(DELAY_JOIN_MESSAGE.getPath())) { - configuration.set(DELAY_JOIN_MESSAGE.getPath(), true); - ConsoleLogger.info("Renamed " + oldDelayJoinPath + " to " + DELAY_JOIN_MESSAGE.getPath()); + } + return hasMigrated; + } + + /** + * Detect old "force spawn loc on join" and "force spawn on these worlds" settings and moves them + * to the new paths. + * + * @param configuration The file configuration + * @return True if the configuration has changed, false otherwise + */ + private static boolean migrateForceSpawnSettings(FileConfiguration configuration) { + Property oldForceLocEnabled = Property.newProperty( + "settings.restrictions.ForceSpawnLocOnJoinEnabled", false); + Property> oldForceWorlds = Property.newListProperty( + "settings.restrictions.ForceSpawnOnTheseWorlds", "world", "world_nether", "world_the_ed"); + + return moveProperty(oldForceLocEnabled, FORCE_SPAWN_LOCATION_AFTER_LOGIN, configuration) + | moveProperty(oldForceWorlds, FORCE_SPAWN_ON_WORLDS, configuration); + } + + /** + * Checks for an old property path and moves it to a new path if present. + * + * @param oldProperty The old property (create a temporary {@link Property} object with the path) + * @param newProperty The new property to move the value to + * @param configuration The file configuration + * @param The type of the property + * @return True if a migration has been done, false otherwise + */ + private static boolean moveProperty(Property oldProperty, + Property newProperty, + FileConfiguration configuration) { + if (configuration.contains(oldProperty.getPath())) { + ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath()); + if (!configuration.contains(newProperty.getPath())) { + ConsoleLogger.info("Renamed " + oldProperty.getPath() + " to " + newProperty.getPath()); + configuration.set(newProperty.getPath(), oldProperty.getFromFile(configuration)); } return true; } diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java index 59693a98..ecb1d379 100644 --- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java +++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java @@ -3,7 +3,11 @@ package fr.xephi.authme.settings; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.hooks.PluginHooks; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.StringUtils; @@ -14,6 +18,7 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; +import javax.inject.Inject; import java.io.File; import java.io.IOException; @@ -25,10 +30,12 @@ import java.io.IOException; * should be taken from. In AuthMe, we can distinguish between the regular spawn and a "first spawn", * to which players will be teleported who have joined for the first time. */ -public class SpawnLoader { +public class SpawnLoader implements Reloadable { private final File authMeConfigurationFile; + private final NewSetting settings; private final PluginHooks pluginHooks; + private final DataSource dataSource; private FileConfiguration authMeConfiguration; private String[] spawnPriority; private Location essentialsSpawn; @@ -39,22 +46,26 @@ public class SpawnLoader { * @param pluginFolder The AuthMe data folder * @param settings The setting instance * @param pluginHooks The plugin hooks instance + * @param dataSource The plugin auth database instance */ - public SpawnLoader(File pluginFolder, NewSetting settings, PluginHooks pluginHooks) { + @Inject + public SpawnLoader(@DataFolder File pluginFolder, NewSetting settings, PluginHooks pluginHooks, + DataSource dataSource) { File spawnFile = new File(pluginFolder, "spawn.yml"); // TODO ljacqu 20160312: Check if resource could be copied and handle the case if not FileUtils.copyFileFromResource(spawnFile, "spawn.yml"); this.authMeConfigurationFile = new File(pluginFolder, "spawn.yml"); + this.settings = settings; this.pluginHooks = pluginHooks; - initialize(settings); + this.dataSource = dataSource; + reload(); } /** - * Retrieve the relevant settings and load the AuthMe spawn.yml file. - * - * @param settings The settings instance + * (Re)loads the spawn file and relevant settings. */ - public void initialize(NewSetting settings) { + @Override + public void reload() { spawnPriority = settings.getProperty(RestrictionSettings.SPAWN_PRIORITY).split(","); authMeConfiguration = YamlConfiguration.loadConfiguration(authMeConfigurationFile); loadEssentialsSpawn(); @@ -149,7 +160,7 @@ public class SpawnLoader { } break; case "multiverse": - if (Settings.multiverse) { + if (settings.getProperty(HooksSettings.MULTIVERSE)) { spawnLoc = pluginHooks.getMultiverseSpawn(world); } break; @@ -161,7 +172,7 @@ public class SpawnLoader { if (PlayerCache.getInstance().isAuthenticated(playerNameLower)) { spawnLoc = getSpawn(); } else if (getFirstSpawn() != null && (!player.hasPlayedBefore() || - !plugin.getDataSource().isAuthAvailable(playerNameLower))) { + !dataSource.isAuthAvailable(playerNameLower))) { spawnLoc = getFirstSpawn(); } else { spawnLoc = getSpawn(); @@ -196,7 +207,6 @@ public class SpawnLoader { } private boolean saveAuthMeConfig() { - // TODO ljacqu 20160312: Investigate whether this utility should be put in a Utils class try { authMeConfiguration.save(authMeConfigurationFile); return true; diff --git a/src/main/java/fr/xephi/authme/settings/domain/EnumProperty.java b/src/main/java/fr/xephi/authme/settings/domain/EnumProperty.java index 14d09329..3e3d3e3b 100644 --- a/src/main/java/fr/xephi/authme/settings/domain/EnumProperty.java +++ b/src/main/java/fr/xephi/authme/settings/domain/EnumProperty.java @@ -24,7 +24,7 @@ class EnumProperty> extends Property { return getDefaultValue(); } E mappedValue = mapToEnum(textValue); - return mappedValue != null ? mappedValue : getDefaultValue(); + return mappedValue == null ? getDefaultValue() : mappedValue; } @Override diff --git a/src/main/java/fr/xephi/authme/settings/domain/Property.java b/src/main/java/fr/xephi/authme/settings/domain/Property.java index 1d5100af..a92d3222 100644 --- a/src/main/java/fr/xephi/authme/settings/domain/Property.java +++ b/src/main/java/fr/xephi/authme/settings/domain/Property.java @@ -3,6 +3,7 @@ package fr.xephi.authme.settings.domain; import org.bukkit.configuration.file.FileConfiguration; import org.yaml.snakeyaml.Yaml; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -33,6 +34,17 @@ public abstract class Property { return new StringListProperty(path, defaultValues); } + /** + * Create a new String list property where all values are lowercase. + * + * @param path The property's path + * @param defaultValues The items in the default list + * @return The created list property + */ + public static Property> newLowercaseListProperty(String path, String... defaultValues) { + return new LowercaseStringListProperty(path, defaultValues); + } + /** * Create a new enum property. * @@ -165,7 +177,7 @@ public abstract class Property { /** * String list property. */ - private static final class StringListProperty extends Property> { + private static class StringListProperty extends Property> { public StringListProperty(String path, String[] defaultValues) { super(path, Arrays.asList(defaultValues)); @@ -196,4 +208,28 @@ public abstract class Property { } } + /** + * Lowercase String list property. + */ + private static final class LowercaseStringListProperty extends StringListProperty { + + public LowercaseStringListProperty(String path, String[] defaultValues) { + super(path, defaultValues); + } + + @Override + public List getFromFile(FileConfiguration configuration) { + if (!configuration.isList(getPath())) { + return getDefaultValue(); + } + + // make sure all elements are lowercase + List lowercaseList = new ArrayList<>(); + for (String element : configuration.getStringList(getPath())) { + lowercaseList.add(element.toLowerCase()); + } + + return lowercaseList; + } + } } 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 87a8ef03..eedce464 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -32,7 +32,7 @@ public class DatabaseSettings implements SettingsClass { @Comment("Password about Database Connection Infos") public static final Property MYSQL_PASSWORD = - newProperty("DataSource.mySQLPassword", "123456"); + newProperty("DataSource.mySQLPassword", "12345"); @Comment("Database Name, use with converters or as SQLITE database name") public static final Property MYSQL_DATABASE = 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 087bb30d..01e11a9c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java @@ -25,7 +25,7 @@ public class HooksSettings implements SettingsClass { @Comment("Do we need to disable Essentials SocialSpy on join?") public static final Property DISABLE_SOCIAL_SPY = - newProperty("Hooks.disableSocialSpy", false); + newProperty("Hooks.disableSocialSpy", true); @Comment("Do we need to force /motd Essentials command on join?") public static final Property USE_ESSENTIALS_MOTD = 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 a4792f91..312f3f51 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java @@ -19,20 +19,20 @@ public class ProtectionSettings implements SettingsClass { @Comment({"Countries allowed to join the server and register, see http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/ for countries' codes", "PLEASE USE QUOTES!"}) public static final Property> COUNTRIES_WHITELIST = - newListProperty("Protection.countries", "US", "GB", "A1"); + newListProperty("Protection.countries", "US", "GB"); @Comment({"Countries not allowed to join the server and register", "PLEASE USE QUOTES!"}) public static final Property> COUNTRIES_BLACKLIST = - newListProperty("Protection.countriesBlacklist"); + newListProperty("Protection.countriesBlacklist", "A1"); @Comment("Do we need to enable automatic antibot system?") public static final Property ENABLE_ANTIBOT = - newProperty("Protection.enableAntiBot", false); + newProperty("Protection.enableAntiBot", true); @Comment("Max number of players allowed to login in 5 secs before the AntiBot system is enabled automatically") public static final Property ANTIBOT_SENSIBILITY = - newProperty("Protection.antiBotSensibility", 5); + newProperty("Protection.antiBotSensibility", 10); @Comment("Duration in minutes of the antibot automatic system") public static final Property ANTIBOT_DURATION = 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 134f9e17..3fd7b40b 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -7,6 +7,7 @@ import fr.xephi.authme.settings.domain.SettingsClass; import java.util.List; import static fr.xephi.authme.settings.domain.Property.newListProperty; +import static fr.xephi.authme.settings.domain.Property.newLowercaseListProperty; import static fr.xephi.authme.settings.domain.Property.newProperty; public class RestrictionSettings implements SettingsClass { @@ -22,16 +23,10 @@ public class RestrictionSettings implements SettingsClass { public static final Property HIDE_CHAT = newProperty("settings.restrictions.hideChat", false); - @Comment({ - "Allow unlogged users to use all the commands if registration is not forced!", - "WARNING: use this only if you need it!"}) - public static final Property ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL = - newProperty("settings.restrictions.allowAllCommandsIfRegistrationIsOptional", false); - @Comment("Allowed commands for unauthenticated players") public static final Property> ALLOW_COMMANDS = - newListProperty("settings.restrictions.allowCommands", - "login", "register", "l", "reg", "email", "captcha"); + newLowercaseListProperty("settings.restrictions.allowCommands", + "/login", "/register", "/l", "/reg", "/email", "/captcha"); @Comment({ "Max number of allowed registrations per IP", @@ -61,7 +56,14 @@ public class RestrictionSettings implements SettingsClass { "This is different from \"teleportUnAuthedToSpawn\" that teleport player", "back to his quit location after the authentication."}) public static final Property FORCE_SPAWN_LOCATION_AFTER_LOGIN = - newProperty("settings.restrictions.ForceSpawnLocOnJoinEnabled", false); + newProperty("settings.restrictions.ForceSpawnLocOnJoin.enabled", false); + + @Comment({ + "WorldNames where we need to force the spawn location", + "Case-sensitive!"}) + public static final Property> FORCE_SPAWN_ON_WORLDS = + newListProperty("settings.restrictions.ForceSpawnLocOnJoin.worlds", + "world", "world_nether", "world_the_end"); @Comment("This option will save the quit location of the players.") public static final Property SAVE_QUIT_LOCATION = @@ -80,7 +82,7 @@ public class RestrictionSettings implements SettingsClass { " AllowedRestrictedUser:", " - playername;127.0.0.1"}) public static final Property> ALLOWED_RESTRICTED_USERS = - newListProperty("settings.restrictions.AllowedRestrictedUser"); + newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); @Comment("Should unregistered players be kicked immediately?") public static final Property KICK_NON_REGISTERED = @@ -149,13 +151,6 @@ public class RestrictionSettings implements SettingsClass { public static final Property DISPLAY_OTHER_ACCOUNTS = newProperty("settings.restrictions.displayOtherAccounts", true); - @Comment({ - "WorldNames where we need to force the spawn location for ForceSpawnLocOnJoinEnabled", - "Case-sensitive!"}) - public static final Property> FORCE_SPAWN_ON_WORLDS = - newListProperty("settings.restrictions.ForceSpawnOnTheseWorlds", - "world", "world_nether", "world_the_end"); - @Comment("Ban ip when the ip is not the ip registered in database") public static final Property BAN_UNKNOWN_IP = newProperty("settings.restrictions.banUnsafedIP", false); @@ -194,7 +189,7 @@ public class RestrictionSettings implements SettingsClass { "It is case-sensitive!" }) public static final Property> UNRESTRICTED_NAMES = - newListProperty("settings.unrestrictions.UnrestrictedName"); + newLowercaseListProperty("settings.unrestrictions.UnrestrictedName"); private RestrictionSettings() { diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index 9a52ca82..ddc98dbd 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -7,7 +7,7 @@ import fr.xephi.authme.settings.domain.SettingsClass; import java.util.List; -import static fr.xephi.authme.settings.domain.Property.newListProperty; +import static fr.xephi.authme.settings.domain.Property.newLowercaseListProperty; import static fr.xephi.authme.settings.domain.Property.newProperty; public class SecuritySettings implements SettingsClass { @@ -34,11 +34,11 @@ public class SecuritySettings implements SettingsClass { public static final Property USE_LOGGING = newProperty("Security.console.logConsole", true); - @Comment("Player need to put a captcha when he fails too lot the password") + @Comment("Enable captcha when a player uses wrong password too many times") public static final Property USE_CAPTCHA = newProperty("Security.captcha.useCaptcha", false); - @Comment("Max allowed tries before request a captcha") + @Comment("Max allowed tries before a captcha is required") public static final Property MAX_LOGIN_TRIES_BEFORE_CAPTCHA = newProperty("Security.captcha.maxLoginTry", 5); @@ -98,7 +98,20 @@ public class SecuritySettings implements SettingsClass { "- '123456'", "- 'password'"}) public static final Property> UNSAFE_PASSWORDS = - newListProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321"); + newLowercaseListProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321"); + + @Comment("Tempban a user's IP address if they enter the wrong password too many times") + public static final Property TEMPBAN_ON_MAX_LOGINS = + newProperty("Security.tempban.enableTempban", false); + + @Comment("How many times a user can attempt to login before their IP being tempbanned") + public static final Property MAX_LOGIN_TEMPBAN = + newProperty("Security.tempban.maxLoginTries", 10); + + @Comment({"The length of time a IP address will be tempbanned in minutes", + "Default: 480 minutes, or 8 hours"}) + public static final Property TEMPBAN_LENGTH = + newProperty("Security.tempban.tempbanLength", 480); private SecuritySettings() { } diff --git a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java new file mode 100644 index 00000000..36530c99 --- /dev/null +++ b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java @@ -0,0 +1,112 @@ +package fr.xephi.authme.task; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import javax.inject.Inject; + +import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; + +/** + * Registers tasks associated with a LimboPlayer. + */ +public class LimboPlayerTaskManager { + + @Inject + private Messages messages; + + @Inject + private NewSetting 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.getLimboPlayer(name); + if (limboPlayer == null) { + ConsoleLogger.info("LimboPlayer for '" + name + "' is not available"); + } else { + cancelTask(limboPlayer.getMessageTask()); + BukkitTask messageTask = bukkitService.runTask(new MessageTask(name, messages.retrieve(key), + interval, bukkitService, limboCache, playerCache)); + 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.getLimboPlayer(player.getName()); + if (limboPlayer == null) { + ConsoleLogger.info("LimboPlayer 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 settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION) + ? MessageKey.REGISTER_EMAIL_MESSAGE + : 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(); + } + } +} diff --git a/src/main/java/fr/xephi/authme/task/MessageTask.java b/src/main/java/fr/xephi/authme/task/MessageTask.java index e09d5039..590913db 100644 --- a/src/main/java/fr/xephi/authme/task/MessageTask.java +++ b/src/main/java/fr/xephi/authme/task/MessageTask.java @@ -2,53 +2,51 @@ package fr.xephi.authme.task; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; import fr.xephi.authme.util.BukkitService; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; +import static fr.xephi.authme.util.BukkitService.TICKS_PER_SECOND; + /** + * Message shown to a player in a regular interval as long as he is not logged in. */ public class MessageTask implements Runnable { - private final BukkitService bukkitService; private final String name; - private final String[] msg; + private final String[] message; private final int interval; + private final BukkitService bukkitService; + private final LimboCache limboCache; + private final PlayerCache playerCache; /* * Constructor. */ - public MessageTask(BukkitService bukkitService, String name, String[] lines, int interval) { - this.bukkitService = bukkitService; + public MessageTask(String name, String[] lines, int interval, BukkitService bukkitService, + LimboCache limboCache, PlayerCache playerCache) { this.name = name; - this.msg = lines; + this.message = lines; this.interval = interval; - } - - /* - * Constructor. - */ - public MessageTask(BukkitService bukkitService, Messages messages, String name, MessageKey messageKey, - int interval) { - this(bukkitService, name, messages.retrieve(messageKey), interval); + this.bukkitService = bukkitService; + this.limboCache = limboCache; + this.playerCache = playerCache; } @Override public void run() { - if (PlayerCache.getInstance().isAuthenticated(name)) { + if (playerCache.isAuthenticated(name)) { return; } for (Player player : bukkitService.getOnlinePlayers()) { if (player.getName().equalsIgnoreCase(name)) { - for (String ms : msg) { + for (String ms : message) { player.sendMessage(ms); } - BukkitTask nextTask = bukkitService.runTaskLater(this, interval * 20); - if (LimboCache.getInstance().hasLimboPlayer(name)) { - LimboCache.getInstance().getLimboPlayer(name).setMessageTask(nextTask); + BukkitTask nextTask = bukkitService.runTaskLater(this, interval * TICKS_PER_SECOND); + if (limboCache.hasLimboPlayer(name)) { + limboCache.getLimboPlayer(name).setMessageTask(nextTask); } return; } diff --git a/src/main/java/fr/xephi/authme/task/PurgeTask.java b/src/main/java/fr/xephi/authme/task/PurgeTask.java new file mode 100644 index 00000000..97ffcc88 --- /dev/null +++ b/src/main/java/fr/xephi/authme/task/PurgeTask.java @@ -0,0 +1,158 @@ +package fr.xephi.authme.task; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.PurgeSettings; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class PurgeTask extends BukkitRunnable { + + //how many players we should check for each tick + private static final int INTERVALL_CHECK = 5; + + private final AuthMe plugin; + private final NewSetting newSetting; + + private final UUID sender; + private final Set toPurge; + + private final OfflinePlayer[] offlinePlayers; + + private final boolean autoPurging; + private final int totalPurgeCount; + + private int currentPage = 0; + + public PurgeTask(AuthMe plugin, CommandSender sender, Set purged) { + this(plugin, sender, purged, false, Bukkit.getOfflinePlayers()); + } + + public PurgeTask(AuthMe plugin, CommandSender sender, Set purged, Set offlinePlayers) { + this(plugin, sender, purged, false + , offlinePlayers.toArray(new OfflinePlayer[offlinePlayers.size()])); + } + + public PurgeTask(AuthMe plugin, CommandSender sender, Set purged + , boolean autoPurge, OfflinePlayer[] offlinePlayers) { + this.plugin = plugin; + this.newSetting = plugin.getSettings(); + + if (sender instanceof Player) { + this.sender = ((Player) sender).getUniqueId(); + } else { + this.sender = null; + } + + this.toPurge = purged; + this.totalPurgeCount = purged.size(); + this.autoPurging = autoPurge; + this.offlinePlayers = offlinePlayers; + + //this is commented out because I assume all players in the database already have an lowercase name +// toPurge = new HashSet<>(purged.size()); + //make a new list with lowercase names to make the username test based on a hash +// for (String username : purged) { +// toPurge.add(username.toLowerCase()); +// } + } + + @Override + public void run() { + if (toPurge.isEmpty()) { + //everything was removed + finish(); + return; + } + + Set playerPortion = new HashSet(INTERVALL_CHECK); + Set namePortion = new HashSet(INTERVALL_CHECK); + for (int i = 0; i < INTERVALL_CHECK; i++) { + int nextPosition = (currentPage * INTERVALL_CHECK) + i; + if (offlinePlayers.length >= nextPosition) { + //no more offline players on this page + break; + } + + OfflinePlayer offlinePlayer = offlinePlayers[nextPosition]; + String offlineName = offlinePlayer.getName(); + //remove to speed up later lookups + if (toPurge.remove(offlineName.toLowerCase())) { + playerPortion.add(offlinePlayer); + namePortion.add(offlinePlayer.getName()); + } + } + + if (!toPurge.isEmpty() && playerPortion.isEmpty()) { + ConsoleLogger.info("Finished lookup up offlinePlayers. Begin looking purging player names only"); + + //we went through all offlineplayers but there are still names remaining + namePortion.addAll(toPurge); + toPurge.clear(); + } + + currentPage++; + purgeData(playerPortion, namePortion); + if (currentPage % 20 == 0) { + int completed = totalPurgeCount - toPurge.size(); + sendMessage("[AuthMe] Purge progress " + completed + '/' + totalPurgeCount); + } + } + + private void purgeData(Set playerPortion, Set namePortion) { + // Purge other data + if (newSetting.getProperty(PurgeSettings.REMOVE_ESSENTIALS_FILES) + && plugin.getPluginHooks().isEssentialsAvailable()) { + plugin.dataManager.purgeEssentials(playerPortion); + } + + if (newSetting.getProperty(PurgeSettings.REMOVE_PLAYER_DAT)) { + plugin.dataManager.purgeDat(playerPortion); + } + + if (newSetting.getProperty(PurgeSettings.REMOVE_LIMITED_CREATIVE_INVENTORIES)) { + plugin.dataManager.purgeLimitedCreative(namePortion); + } + + if (newSetting.getProperty(PurgeSettings.REMOVE_ANTI_XRAY_FILE)) { + plugin.dataManager.purgeAntiXray(namePortion); + } + + if (newSetting.getProperty(PurgeSettings.REMOVE_PERMISSIONS)) { + plugin.dataManager.purgePermissions(playerPortion); + } + } + + private void finish() { + cancel(); + + // Show a status message + sendMessage(ChatColor.GREEN + "[AuthMe] Database has been purged correctly"); + + ConsoleLogger.info("AutoPurge Finished!"); + if (autoPurging) { + plugin.notifyAutoPurgeEnd(); + } + } + + private void sendMessage(String message) { + if (sender == null) { + Bukkit.getConsoleSender().sendMessage(message); + } else { + Player player = Bukkit.getPlayer(sender); + if (player != null) { + player.sendMessage(message); + } + } + } +} diff --git a/src/main/java/fr/xephi/authme/task/TimeoutTask.java b/src/main/java/fr/xephi/authme/task/TimeoutTask.java index b304632d..8aa719cd 100644 --- a/src/main/java/fr/xephi/authme/task/TimeoutTask.java +++ b/src/main/java/fr/xephi/authme/task/TimeoutTask.java @@ -1,34 +1,34 @@ package fr.xephi.authme.task; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; import org.bukkit.entity.Player; +/** + * Kicks a player if he hasn't logged in (scheduled to run after a configured delay). + */ public class TimeoutTask implements Runnable { - private final String name; - private final Messages m; private final Player player; + private final String message; + private final PlayerCache playerCache; /** * Constructor for TimeoutTask. * - * @param plugin AuthMe - * @param name String - * @param player Player + * @param player the player to check + * @param message the kick message + * @param playerCache player cache instance */ - public TimeoutTask(AuthMe plugin, String name, Player player) { - this.m = plugin.getMessages(); - this.name = name; + public TimeoutTask(Player player, String message, PlayerCache playerCache) { + this.message = message; this.player = player; + this.playerCache = playerCache; } @Override public void run() { - if (!PlayerCache.getInstance().isAuthenticated(name)) { - player.kickPlayer(m.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR)); + if (!playerCache.isAuthenticated(player.getName())) { + player.kickPlayer(message); } } } diff --git a/src/main/java/fr/xephi/authme/util/BukkitService.java b/src/main/java/fr/xephi/authme/util/BukkitService.java index ea405d80..8b94fe49 100644 --- a/src/main/java/fr/xephi/authme/util/BukkitService.java +++ b/src/main/java/fr/xephi/authme/util/BukkitService.java @@ -2,16 +2,22 @@ package fr.xephi.authme.util; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +import org.bukkit.BanEntry; +import org.bukkit.BanList; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.event.Event; import org.bukkit.scheduler.BukkitTask; +import javax.inject.Inject; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.Set; /** @@ -28,7 +34,8 @@ public class BukkitService { private final boolean getOnlinePlayersIsCollection; private Method getOnlinePlayers; - public BukkitService(AuthMe authMe) { + @Inject + BukkitService(AuthMe authMe) { this.authMe = authMe; getOnlinePlayersIsCollection = initializeOnlinePlayersIsCollectionField(); } @@ -156,7 +163,7 @@ public class BukkitService { } else if (obj instanceof Player[]) { return Arrays.asList((Player[]) obj); } else { - String type = (obj != null) ? obj.getClass().getName() : "null"; + String type = (obj == null) ? "null" : obj.getClass().getName(); ConsoleLogger.showError("Unknown list of online players of type " + type); } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { @@ -165,6 +172,27 @@ public class BukkitService { return Collections.emptyList(); } + /** + * Calls an event with the given details. + * + * @param event Event details + * @throws IllegalStateException Thrown when an asynchronous event is + * fired from synchronous code. + */ + public void callEvent(Event event) { + Bukkit.getPluginManager().callEvent(event); + } + + /** + * Gets the world with the given name. + * + * @param name the name of the world to retrieve + * @return a world with the given name, or null if none exists + */ + public World getWorld(String name) { + return Bukkit.getWorld(name); + } + /** * Method run upon initialization to verify whether or not the Bukkit implementation * returns the online players as a Collection. @@ -181,4 +209,20 @@ public class BukkitService { return false; } + /** + * Adds a ban to the this list. If a previous ban exists, this will + * update the previous entry. + * + * @param ip the ip of the ban + * @param reason reason for the ban, null indicates implementation default + * @param expires date for the ban's expiration (unban), or null to imply + * forever + * @param source source of the ban, null indicates implementation default + * @return the entry for the newly created ban, or the entry for the + * (updated) previous ban + */ + public BanEntry banIp(String ip, String reason, Date expires, String source) { + return Bukkit.getServer().getBanList(BanList.Type.IP).addBan(ip, reason, expires, source); + } + } diff --git a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java index 83594086..49a57cf3 100644 --- a/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java +++ b/src/main/java/fr/xephi/authme/util/GeoLiteAPI.java @@ -39,18 +39,21 @@ public class GeoLiteAPI { } final File pluginFolder = AuthMe.getInstance().getDataFolder(); final File data = new File(pluginFolder, "GeoIP.dat"); - boolean dataIsOld = (System.currentTimeMillis() - data.lastModified()) > TimeUnit.DAYS.toMillis(30); - if (dataIsOld && !data.delete()) { - ConsoleLogger.showError("Failed to delete GeoLiteAPI database"); - } if (data.exists()) { - try { - lookupService = new LookupService(data); - ConsoleLogger.info(LICENSE); - return true; - } catch (IOException e) { - ConsoleLogger.logException("Failed to load GeoLiteAPI database", e); - return false; + boolean dataIsOld = (System.currentTimeMillis() - data.lastModified()) > TimeUnit.DAYS.toMillis(30); + if (!dataIsOld) { + try { + lookupService = new LookupService(data); + ConsoleLogger.info(LICENSE); + return true; + } catch (IOException e) { + ConsoleLogger.logException("Failed to load GeoLiteAPI database", e); + return false; + } + } else { + if (!data.delete()) { + ConsoleLogger.showError("Failed to delete GeoLiteAPI database"); + } } } // Ok, let's try to download the data file! diff --git a/src/main/java/fr/xephi/authme/util/MigrationService.java b/src/main/java/fr/xephi/authme/util/MigrationService.java index 7bf028f6..cd9bb382 100644 --- a/src/main/java/fr/xephi/authme/util/MigrationService.java +++ b/src/main/java/fr/xephi/authme/util/MigrationService.java @@ -31,7 +31,6 @@ public final class MigrationService { * @param dataSource The data source * @param authmeSha256 Instance to the AuthMe SHA256 encryption method implementation */ - @SuppressWarnings("deprecation") public static void changePlainTextToSha256(NewSetting settings, DataSource dataSource, SHA256 authmeSha256) { if (HashAlgorithm.PLAINTEXT == settings.getProperty(SecuritySettings.PASSWORD_HASH)) { @@ -62,7 +61,6 @@ public final class MigrationService { * @param dataSource The data source * @return The converted datasource (SQLite), or null if no migration was necessary */ - @SuppressWarnings("deprecation") public static DataSource convertFlatfileToSqlite(NewSetting settings, DataSource dataSource) { if (DataSourceType.FILE == settings.getProperty(DatabaseSettings.BACKEND)) { ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated; it will be changed " diff --git a/src/main/java/fr/xephi/authme/util/Profiler.java b/src/main/java/fr/xephi/authme/util/Profiler.java deleted file mode 100644 index 73bdf5e4..00000000 --- a/src/main/java/fr/xephi/authme/util/Profiler.java +++ /dev/null @@ -1,136 +0,0 @@ -package fr.xephi.authme.util; - -import java.text.DecimalFormat; - -public class Profiler { - - /** - * Defines the past time in milliseconds. - */ - private long time = 0; - - /** - * Defines the time in milliseconds the profiler last started at. - */ - private long start = -1; - - /** - * Constructor. This won't start the profiler immediately. - */ - public Profiler() { - this(false); - } - - /** - * Constructor. - * - * @param start True to immediately start the profiler. - */ - public Profiler(boolean start) { - // Should the timer be started - if (start) - start(); - } - - /** - * Start the profiler. - * - * @return True if the profiler was started, false otherwise possibly due to an error. - * True will also be returned if the profiler was started already. - */ - public boolean start() { - // Make sure the timer isn't started already - if (isActive()) - return true; - - // Set the start time - this.start = System.currentTimeMillis(); - return true; - } - - /** - * This will start the profiler if it's not active, or will stop the profiler if it's currently active. - * - * @return True if the profiler has been started, false if the profiler has been stopped. - */ - public boolean pause() { - // Toggle the profiler state - if (isStarted()) - stop(); - else - start(); - - // Return the result - return isStarted(); - } - - /** - * Stop the profiler if it's active. - * - * @return True will be returned if the profiler was stopped while it was active. False will be returned if the - * profiler was stopped already. - */ - public boolean stop() { - // Make sure the profiler is active - if (!isActive()) - return false; - - // Stop the profiler, calculate the passed time - this.time += System.currentTimeMillis() - this.start; - this.start = -1; - return true; - } - - /** - * Check whether the profiler has been started. The profiler doesn't need to be active right now. - * - * @return True if the profiler was started, false otherwise. - */ - public boolean isStarted() { - return isActive() || this.time > 0; - } - - /** - * Check whether the profiler is currently active. - * - * @return True if the profiler is active, false otherwise. - */ - public boolean isActive() { - return this.start >= 0; - } - - /** - * Get the passed time in milliseconds. - * - * @return The passed time in milliseconds. - */ - public long getTime() { - // Check whether the profiler is currently active - if (isActive()) - return this.time + (System.currentTimeMillis() - this.start); - return this.time; - } - - /** - * Get the passed time in a formatted string. - * - * @return The passed time in a formatted string. - */ - public String getTimeFormatted() { - // Get the passed time - long time = getTime(); - - // Return the time if it's less than one millisecond - if (time <= 0) - return "<1 ms"; - - // Return the time in milliseconds - if (time < 1000) - return time + " ms"; - - // Convert the time into seconds with a single decimal - double timeSeconds = ((double) time) / 1000; - DecimalFormat df = new DecimalFormat("#0.0"); - return df.format(timeSeconds) + " s"; - } -} diff --git a/src/main/java/fr/xephi/authme/util/TeleportationService.java b/src/main/java/fr/xephi/authme/util/TeleportationService.java new file mode 100644 index 00000000..06c7558d --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/TeleportationService.java @@ -0,0 +1,142 @@ +package fr.xephi.authme.util; + +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.events.AbstractTeleportEvent; +import fr.xephi.authme.events.AuthMeTeleportEvent; +import fr.xephi.authme.events.FirstSpawnTeleportEvent; +import fr.xephi.authme.events.SpawnTeleportEvent; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.HashSet; +import java.util.Set; + +import static fr.xephi.authme.settings.properties.RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN; + +/** + * Handles teleportation (placement of player to spawn). + */ +public class TeleportationService implements Reloadable { + + @Inject + private NewSetting settings; + + @Inject + private BukkitService bukkitService; + + @Inject + private SpawnLoader spawnLoader; + + @Inject + private PlayerCache playerCache; + + private Set spawnOnLoginWorlds; + + TeleportationService() { } + + + @PostConstruct + @Override + public void reload() { + // Use a Set for better performance with #contains() + spawnOnLoginWorlds = new HashSet<>(settings.getProperty(RestrictionSettings.FORCE_SPAWN_ON_WORLDS)); + } + + public void teleportOnJoin(final Player player) { + if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { + return; + } else if (teleportToFirstSpawn(player)) { + return; + } + + if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN) || mustForceSpawnAfterLogin(player.getWorld().getName())) { + teleportToSpawn(player, playerCache.isAuthenticated(player.getName())); + } + } + + public void teleportOnLogin(final Player player, PlayerAuth auth, LimboPlayer limbo) { + if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { + return; + } + + // The world in LimboPlayer is from where the player comes, before any teleportation by AuthMe + String worldName = limbo.getLoc().getWorld().getName(); + if (mustForceSpawnAfterLogin(worldName)) { + teleportToSpawn(player, true); + } else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) { + if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && auth.getQuitLocY() != 0) { + Location location = buildLocationFromAuth(player, auth); + teleportBackFromSpawn(player, location); + } else { + teleportBackFromSpawn(player, limbo.getLoc()); + } + } + } + + private boolean mustForceSpawnAfterLogin(String worldName) { + return settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN) + && spawnOnLoginWorlds.contains(worldName); + } + + private Location buildLocationFromAuth(Player player, PlayerAuth auth) { + World world = bukkitService.getWorld(auth.getWorld()); + if (world == null) { + world = player.getWorld(); + } + return new Location(world, auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ()); + } + + private boolean teleportToFirstSpawn(final Player player) { + if (player.hasPlayedBefore()) { + return false; + } + Location firstSpawn = spawnLoader.getFirstSpawn(); + if (firstSpawn == null) { + return false; + } + + performTeleportation(player, new FirstSpawnTeleportEvent(player, firstSpawn)); + return true; + } + + private void teleportBackFromSpawn(final Player player, final Location location) { + performTeleportation(player, new AuthMeTeleportEvent(player, location)); + } + + private void teleportToSpawn(final Player player, final boolean isAuthenticated) { + final Location spawnLoc = spawnLoader.getSpawnLocation(player); + performTeleportation(player, new SpawnTeleportEvent(player, spawnLoc, isAuthenticated)); + } + + /** + * 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. + * + * @param player the player to teleport + * @param event the event to emit and according to which to teleport + */ + private void performTeleportation(final Player player, final AbstractTeleportEvent event) { + bukkitService.scheduleSyncDelayedTask(new Runnable() { + @Override + public void run() { + bukkitService.callEvent(event); + if (player.isOnline() && isEventValid(event)) { + player.teleport(event.getTo()); + } + } + }); + } + + private static boolean isEventValid(AbstractTeleportEvent event) { + return !event.isCancelled() && event.getTo() != null && event.getTo().getWorld() != null; + } +} diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 5850645b..ae940656 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -2,16 +2,11 @@ package fr.xephi.authme.util; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.events.AuthMeTeleportEvent; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.OfflinePlayer; -import org.bukkit.World; import org.bukkit.entity.Player; import java.util.Arrays; @@ -26,68 +21,6 @@ public final class Utils { private Utils() { } - /** - * Set the group of a player, by its AuthMe group type. - * - * @param player The player. - * @param group The group type. - * - * @return True if succeeded, false otherwise. False is also returned if groups aren't supported - * with the current permissions system. - */ - public static boolean setGroup(Player player, GroupType group) { - // Check whether the permissions check is enabled - if (!Settings.isPermissionCheckEnabled) { - return false; - } - - // Get the permissions manager, and make sure it's valid - PermissionsManager permsMan = plugin.getPermissionsManager(); - if (permsMan == null) { - ConsoleLogger.showError("Failed to access permissions manager instance, shutting down."); - return false; - } - - // Make sure group support is available - if (!permsMan.hasGroupSupport()) { - ConsoleLogger.showError("The current permissions system doesn't have group support, unable to set group!"); - return false; - } - - switch (group) { - case UNREGISTERED: - // Remove the other group type groups, set the current group - permsMan.removeGroups(player, Arrays.asList(Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); - return permsMan.addGroup(player, Settings.unRegisteredGroup); - - case REGISTERED: - // Remove the other group type groups, set the current group - permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getUnloggedinGroup)); - return permsMan.addGroup(player, Settings.getRegisteredGroup); - - case NOTLOGGEDIN: - // Remove the other group type groups, set the current group - permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup)); - return permsMan.addGroup(player, Settings.getUnloggedinGroup); - - case LOGGEDIN: - // Get the limbo player data - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(player.getName().toLowerCase()); - if (limbo == null) - return false; - - // Get the players group - String realGroup = limbo.getGroup(); - - // Remove the other group types groups, set the real group - permsMan.removeGroups(player, Arrays.asList(Settings.unRegisteredGroup, Settings.getRegisteredGroup, Settings.getUnloggedinGroup)); - return permsMan.addGroup(player, realGroup); - - default: - return false; - } - } - /** * TODO: This method requires better explanation. *

@@ -118,57 +51,15 @@ public final class Utils { return permsMan.addGroup(player, group); } - // TODO: Move to a Manager - public static boolean checkAuth(Player player) { - if (player == null || Utils.isUnrestricted(player)) { - return true; - } - - if (PlayerCache.getInstance().isAuthenticated(player.getName())) { - return true; - } - - if (!Settings.isForcedRegistrationEnabled && !plugin.getDataSource().isAuthAvailable(player.getName())) { - return true; - } - return false; - } - + @Deprecated public static boolean isUnrestricted(Player player) { + // TODO ljacqu 20160602: Checking for Settings.isAllowRestrictedIp is wrong! Nothing in the config suggests + // that this setting has anything to do with unrestricted names return Settings.isAllowRestrictedIp && Settings.getUnrestrictedName.contains(player.getName().toLowerCase()); } - public static void packCoords(double x, double y, double z, String w, final Player pl) { - World theWorld; - if (w.equals("unavailableworld")) { - theWorld = pl.getWorld(); - } else { - theWorld = Bukkit.getWorld(w); - } - if (theWorld == null) { - theWorld = pl.getWorld(); - } - final World world = theWorld; - final Location loc = new Location(world, x, y, z); - - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { - - @Override - public void run() { - AuthMeTeleportEvent tpEvent = new AuthMeTeleportEvent(pl, loc); - plugin.getServer().getPluginManager().callEvent(tpEvent); - if (!tpEvent.isCancelled()) { - pl.teleport(tpEvent.getTo()); - } - } - }); - } - - public static boolean isNPC(Player player) { - return player.hasMetadata("NPC") || plugin.getPluginHooks().isNpcInCombatTagPlus(player); - } - + @Deprecated public static void teleportToSpawn(Player player) { if (Settings.isTeleportToSpawnEnabled && !Settings.noTeleport) { Location spawn = plugin.getSpawnLocation(player); @@ -188,13 +79,6 @@ public final class Utils { } } - public enum GroupType { - UNREGISTERED, - REGISTERED, - NOTLOGGEDIN, - LOGGEDIN - } - /** * Returns the IP of the given player. * diff --git a/src/main/java/fr/xephi/authme/util/ValidationService.java b/src/main/java/fr/xephi/authme/util/ValidationService.java index 95dff810..8e72f852 100644 --- a/src/main/java/fr/xephi/authme/util/ValidationService.java +++ b/src/main/java/fr/xephi/authme/util/ValidationService.java @@ -1,6 +1,7 @@ package fr.xephi.authme.util; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; @@ -12,22 +13,32 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.util.Collection; import java.util.List; +import java.util.regex.Pattern; /** * Validation service. */ -public class ValidationService { +public class ValidationService implements Reloadable { private final NewSetting settings; private final DataSource dataSource; private final PermissionsManager permissionsManager; + private Pattern passwordRegex; + @Inject public ValidationService(NewSetting settings, DataSource dataSource, PermissionsManager permissionsManager) { this.settings = settings; this.dataSource = dataSource; this.permissionsManager = permissionsManager; + reload(); + } + + @Override + public void reload() { + passwordRegex = Pattern.compile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)); } /** @@ -35,23 +46,21 @@ public class ValidationService { * * @param password the password to verify * @param username the username the password is associated with - * @return message key with the password error, or {@code null} if password is valid + * @return the validation result */ - public MessageKey validatePassword(String password, String username) { + public ValidationResult validatePassword(String password, String username) { String passLow = password.toLowerCase(); - if (!passLow.matches(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX))) { - return MessageKey.PASSWORD_MATCH_ERROR; + if (!passwordRegex.matcher(passLow).matches()) { + return new ValidationResult(MessageKey.PASSWORD_CHARACTERS_ERROR, passwordRegex.pattern()); } else if (passLow.equalsIgnoreCase(username)) { - return MessageKey.PASSWORD_IS_USERNAME_ERROR; + return new ValidationResult(MessageKey.PASSWORD_IS_USERNAME_ERROR); } else if (password.length() < settings.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH) || password.length() > settings.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) { - return MessageKey.INVALID_PASSWORD_LENGTH; + return new ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH); } else if (settings.getProperty(SecuritySettings.UNSAFE_PASSWORDS).contains(passLow)) { - // TODO #602 20160312: The UNSAFE_PASSWORDS should be all lowercase - // -> introduce a lowercase String list property type - return MessageKey.PASSWORD_UNSAFE_ERROR; + return new ValidationResult(MessageKey.PASSWORD_UNSAFE_ERROR); } - return null; + return new ValidationResult(); } /** @@ -130,4 +139,44 @@ public class ValidationService { } return false; } + + public static final class ValidationResult { + private final MessageKey messageKey; + private final String[] args; + + /** + * Constructor for a successful validation. + */ + public ValidationResult() { + this.messageKey = null; + this.args = null; + } + + /** + * Constructor for a failed validation. + * + * @param messageKey message key of the validation error + * @param args arguments for the message key + */ + public ValidationResult(MessageKey messageKey, String... args) { + this.messageKey = messageKey; + this.args = args; + } + + /** + * Returns whether an error was found during the validation, i.e. whether the validation failed. + * + * @return true if there is an error, false if the validation was successful + */ + public boolean hasError() { + return messageKey != null; + } + + public MessageKey getMessageKey() { + return messageKey; + } + public String[] getArgs() { + return args; + } + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 17b62b37..99cc3a2b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -69,9 +69,6 @@ settings: allowChat: false # Can not authenticated players see the chat log? hideChat: false - # WARNING: use this only if you need it! - # Allow unlogged users to use all the commands if registration is not forced! - allowAllCommandsIfRegistrationIsOptional: false # Commands allowed when a player is not authenticated allowCommands: - /login @@ -88,12 +85,19 @@ settings: # due to "Logged in from another Location" # This setting will prevent potetial security exploits. ForceSingleSession: true - # If enabled, every player will be teleported to the world spawnpoint - # after successful authentication. - # The quit location of the player will be overwritten. - # This is different from "teleportUnAuthedToSpawn" that teleport player - # back to his quit location after the authentication. - ForceSpawnLocOnJoinEnabled: false + ForceSpawnLocOnJoin: + # If enabled, every player will be teleported to the world spawnpoint + # after successful authentication. + # The quit location of the player will be overwritten. + # This is different from "teleportUnAuthedToSpawn" that teleport player + # back to his quit location after the authentication. + enabled: false + # WorldNames where we need to force the spawn location + # Case-sensitive! + worlds: + - 'world' + - 'world_nether' + - 'world_the_end' # This option will save the quit location of the players. SaveQuitLocation: false # To activate the restricted user feature you need @@ -101,12 +105,11 @@ settings: # AllowedRestrctedUser field. AllowRestrictedUser: false # The restricted user feature will kick players listed below - # if they dont match of the defined ip address. + # if they don't match of the defined ip address. # Example: # AllowedRestrictedUser: # - playername;127.0.0.1 - AllowedRestrictedUser: - - playername;127.0.0.1 + AllowedRestrictedUser: [] # Should unregistered players be kicked immediately? kickNonRegistered: false # Should players be kicked on wrong password? @@ -143,12 +146,6 @@ settings: # Should we display all other accounts from a player when he joins? # permission: /authme.admin.accounts displayOtherAccounts: true - # WorldNames where we need to force the spawn location for ForceSpawnLocOnJoinEnabled - # CASE SENSITIVE - ForceSpawnOnTheseWorlds: - - world - - world_nether - - world_the_end # Ban ip when the ip is not the ip registered in database banUnsafedIP: false # Spawn Priority, Values : authme, essentials, multiverse, default @@ -262,11 +259,11 @@ settings: # Do we need to broadcast the welcome message to all server or only to the player? set true for server or false for player broadcastWelcomeMessage: false # Should we delay the join message and display it once the player has logged in? - delayJoinMessage: true + delayJoinMessage: false # Should we remove join messages altogether? - removeJoinMessage: true + removeJoinMessage: false # Should we remove leave messages? - removeLeaveMessage: true + removeLeaveMessage: false # Do we need to add potion effect Blinding before login/register? applyBlindEffect: false # Do we need to prevent people to login with another case? @@ -306,7 +303,7 @@ BackupSystem: # set Backup at every stop of Server OnServerStop: true # Windows only mysql installation Path - MysqlWindowsPath: 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\' + MysqlWindowsPath: 'C:\Program Files\MySQL\MySQL Server 5.1\' Security: SQLProblem: # Stop the server if we can't contact the sql database @@ -324,9 +321,9 @@ Security: # Copy AuthMe log output in a separate file as well? logConsole: true captcha: - # Player need to put a captcha when he fails too lot the password + # Enable captcha when a player uses wrong password too many times useCaptcha: false - # Max allowed tries before request a captcha + # Max allowed tries before a captcha is required maxLoginTry: 5 # Captcha length captchaLength: 5 @@ -334,6 +331,14 @@ Security: # Kick players before stopping the server, that allow us to save position of players, and all needed # information correctly without any corruption. kickPlayersBeforeStopping: true + tempban: + # Tempban a user's IP address if they enter the wrong password too many times + enableTempban: false + # How many times a user can attempt to login before their IP being tempbanned + maxLoginTries: 10 + # The length of time a IP address will be tempbanned in minutes + # Default: 480 minutes, or 8 hours + tempbanLength: 480 Converter: Rakamak: # Rakamak file name @@ -359,7 +364,7 @@ Email: # Random password length RecoveryPasswordLength: 8 # Email subject of password get - mailSubject: 'Your new AuthMe Password' + mailSubject: 'Your new AuthMe password' # Like maxRegPerIp but with email maxRegPerEmail: 1 # Recall players to add an email? @@ -418,8 +423,8 @@ Protection: countriesBlacklist: - 'A1' # Do we need to enable automatic antibot system? - enableAntiBot: false + enableAntiBot: true # Max number of player allowed to login in 5 secs before enable AntiBot system automatically - antiBotSensibility: 5 + antiBotSensibility: 10 # Duration in minutes of the antibot automatic system antiBotDuration: 10 diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 94e32f38..e174e7b9 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -12,6 +12,7 @@ login: '&cВход успешен!' vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' user_regged: '&cПотребителското име е заето!' usage_reg: '&cКоманда: /register парола парола' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fТи достигна максималния брой регистрации!' no_perm: '&cНямаш Достъп!' error: '&fПолучи се грешка; Моля свържете се с админ' @@ -53,12 +54,16 @@ email_send: '[AuthMe] Изпраен е имейл !' country_banned: Твоята държава е забранена в този сървър! antibot_auto_enabled: '[AuthMe] AntiBotMod автоматично включен, открита е потенциална атака!' antibot_auto_disabled: '[AuthMe] AntiBotMod автоматично изключване след %m Минути.' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO email_already_used: '&4The email address is already being used' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' +# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' # TODO invalid_session: '&cYour IP has been changed and your session data has expired!' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index 24662777..4f1c2f11 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -14,6 +14,7 @@ login: '&2Logado com sucesso!' vb_nonActiv: '&cSua conta não foi ativada ainda, olhe seu email!' user_regged: '&cVocê já registrou esse nick!' usage_reg: '&cUse: /register ' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&cVocê excedeu a quantidade de registros por ip!' no_perm: '&4Sem permissão!' error: '&4Um erro ocoreu, contate um administrador!' @@ -63,3 +64,7 @@ two_factor_create: '&2Seu código secreto é %code. Você pode escanear ele daqu email_already_used: '&4Este endereço de email já está em uso' not_owner_error: 'Você não é o dono desta conta. Por favor, tente outro nome!' invalid_name_case: 'Você deve entrar usando %valid, não %invalid.' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index 580a12bc..2937cef7 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -26,6 +26,7 @@ reload: '&cZnovu nacteni nastaveni AuthMe probehlo uspesne.' timeout: '&cCas pro prihlaseni vyprsel!' unsafe_spawn: '&cTvoje pozice pri odpojeni byla nebezpecna, teleportuji na spawn!' invalid_session: '&cChybna data pri cteni pockejte do vyprseni.' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&cJiz jsi prekrocil(a) limit pro pocet uctu z jedne IP.' password_error: '&cHesla se neshoduji!' pass_len: '&cTvoje heslo nedosahuje minimalni delky (4).' @@ -53,11 +54,15 @@ email_send: '[AuthMe] Email pro obnoveni hesla odeslan!' country_banned: 'Vase zeme je na tomto serveru zakazana' antibot_auto_enabled: '[AuthMe] AntiBotMod automaticky spusten z duvodu masivnich pripojeni!' antibot_auto_disabled: '[AuthMe] AntiBotMod automaticky ukoncen po %m minutach, doufejme v konec invaze' -# TODO email_already_used: '&4The email address is already being used' # TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index 4cc59867..c196f3b0 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -11,7 +11,7 @@ login: '&2Erfolgreich eingeloggt!' vb_nonActiv: '&cDein Account wurde noch nicht aktiviert. Bitte prüfe deine E-Mails!' user_regged: '&cDieser Benutzername ist schon vergeben' usage_reg: '&cBenutze: /register ' -max_reg: '&cDu hast die maximale Anzahl an Accounts erreicht (%max_acc/%reg_count).' +max_reg: '&cDu hast die maximale Anzahl an Accounts erreicht (%reg_count/%max_acc %reg_names).' no_perm: '&4Du hast keine Rechte, um diese Aktion auszuführen!' error: '&4Ein Fehler ist aufgetreten. Bitte kontaktiere einen Administrator.' login_msg: '&cBitte logge dich ein mit "/login "' @@ -23,6 +23,7 @@ user_unknown: '&cBenutzername nicht registriert!' password_error: '&cPasswörter stimmen nicht überein!' password_error_nick: '&cDu kannst deinen Namen nicht als Passwort verwenden!' password_error_unsafe: '&cPasswort unsicher! Bitte wähle ein anderes.' +password_error_chars: '&4Dein Passwort enthält unerlaubte Zeichen. Zulässige Zeichen: REG_EX' invalid_session: '&cUngültige Session. Bitte starte das Spiel neu oder warte, bis die Session abgelaufen ist.' reg_only: '&4Nur für registrierte Spieler! Bitte besuche http://example.com um dich zu registrieren.' logged_in: '&cBereits eingeloggt!' @@ -61,3 +62,9 @@ two_factor_create: '&2Dein geheimer Code ist %code. Du kannst ihn hier abfragen: email_already_used: '&4Diese E-Mail-Adresse wird bereits genutzt.' invalid_name_case: 'Dein registrierter Benutzername ist &2%valid&f - nicht &4%invalid&f.' not_owner_error: 'Du bist nicht der Besitzer dieses Accounts. Bitte wähle einen anderen Namen!' +denied_chat: '&cDu musst eingeloggt sein, um chatten zu können!' +same_ip_online: 'Ein Spieler mit derselben IP ist bereits online!' +denied_command: '&cUm diesen Befehl zu nutzen musst du authentifiziert sein!' +tempban_max_logins: '&cDu bist wegen zu vielen fehlgeschlagenen Login-Versuchen temporär gebannt!' +accounts_owned_self: 'Du besitzt %count Accounts:' +accounts_owned_other: 'Der Spieler %name hat %count Accounts:' diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index 5b474e2d..f2926eb9 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -1,3 +1,6 @@ +denied_command: '&cIn order to use this command you must be authenticated!' +same_ip_online: 'A player with the same IP is already in game!' +denied_chat: '&cIn order to chat you must be authenticated!' kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' unknown_user: '&cCan''t find the requested user in the database!' unsafe_spawn: '&cYour quit location was unsafe, you have been teleported to the world''s spawnpoint.' @@ -24,6 +27,7 @@ user_unknown: '&cThis user isn''t registered!' password_error: '&cPasswords didn''t match, check them again!' password_error_nick: '&cYou can''t use your name as password, please choose another one...' password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' +password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' invalid_session: '&cYour IP has been changed and your session data has expired!' reg_only: '&4Only registered users can join the server! Please visit http://example.com to register yourself!' logged_in: '&cYou''re already logged in!' @@ -61,3 +65,6 @@ email_already_used: '&4The email address is already being used' two_factor_create: '&2Your secret code is %code. You can scan it from here %url' not_owner_error: 'You are not the owner of this account. Please choose another name!' invalid_name_case: 'You should join using username %valid, not %invalid.' +tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +accounts_owned_self: 'You own %count accounts:' +accounts_owned_other: 'The player %name has %count accounts:' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index df415952..da7b4beb 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -12,6 +12,7 @@ login: '&c¡Sesión iniciada!' vb_nonActiv: '&fTu cuenta no está activada aún, ¡revisa tu correo!' user_regged: '&cUsuario ya registrado' usage_reg: '&cUso: /register Contraseña ConfirmarContraseña' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fHas excedido la cantidad máxima de registros para tu cuenta' no_perm: '&cNo tienes permiso' error: '&fHa ocurrido un error. Por favor contacta al administrador.' @@ -22,6 +23,7 @@ usage_unreg: '&cUso: /unregister contraseña' pwd_changed: '&c¡Contraseña cambiada!' user_unknown: '&cUsuario no registrado' password_error: '&fLas contraseñas no son iguales' +password_error_chars: '&cTu contraseña tiene carácteres no admitidos, los cuales son: REG_EX' invalid_session: '&fLos datos de sesión no corresponden. Por favor espera a terminar la sesión.' reg_only: '&f¡Sólo para jugadores registrados! Por favor visita http://www.example.com/ para registrarte' logged_in: '&c¡Ya has iniciado sesión!' @@ -54,11 +56,14 @@ email_send: '[AuthMe] Correo de recuperación enviado !' country_banned: 'Tu país ha sido baneado de este servidor!' antibot_auto_enabled: '[AuthMe] AntiBotMod activado automáticamente debido a conexiones masivas!' antibot_auto_disabled: '[AuthMe] AntiBotMod desactivado automáticamente luego de %m minutos. Esperamos que haya terminado' -# TODO email_already_used: '&4The email address is already being used' # TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index b490aeeb..d761ffec 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -11,6 +11,7 @@ login: '&cOngi etorri!' vb_nonActiv: '&fZure kontua aktibatu gabe dago, konfirmatu zure emaila!' user_regged: '&cErabiltzailea dagoeneko erregistratua' usage_reg: '&cErabili: /register pasahitza pasahitza' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fKontuko 2 erabiltzaile bakarrik izan ditzakezu' no_perm: '&cBaimenik ez' error: '&fErrorea; Mesedez jarri kontaktuan administratzaile batekin' @@ -47,17 +48,21 @@ email_confirm: '[AuthMe] Konfirmatu zure emaila !' email_changed: '[AuthMe] Emaila aldatua!' email_send: '[AuthMe] Berreskuratze emaila bidalita !' country_banned: '[AuthMe] Zure herrialdea blokeatuta dago zerbitzari honetan' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' # TODO email_already_used: '&4The email address is already being used' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' +# TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' -# TODO invalid_session: '&cYour IP has been changed and your session data has expired!' -# TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' -# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' +# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' # TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO invalid_session: '&cYour IP has been changed and your session data has expired!' +# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' # TODO valid_captcha: '&2Captcha code solved correctly!' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index 80e6cd4f..2a4beacf 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -11,6 +11,7 @@ login: '&cKirjauduit onnistuneesti' vb_nonActiv: '&fKäyttäjäsi ei ole vahvistettu!' user_regged: '&cPelaaja on jo rekisteröity' usage_reg: '&cKäyttötapa: /register salasana salasana' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fSinulla ei ole oikeuksia tehdä enempää pelaajatilejä!' no_perm: '&cEi oikeuksia' error: '&fVirhe: Ota yhteys palveluntarjoojaan!' @@ -50,14 +51,18 @@ email_added: '[AuthMe] Sähköposti lisätty!' email_confirm: '[AuthMe] Vahvistuta sähköposti!' email_changed: '[AuthMe] Sähköposti vaihdettu!' email_send: '[AuthMe] Palautus sähköposti lähetetty!' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' # TODO email_already_used: '&4The email address is already being used' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' +# TODO country_banned: '&4Your country is banned from this server!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO country_banned: '&4Your country is banned from this server!' -# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' +# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' # TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 153cceb4..09691261 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -1,65 +1,69 @@ # Traduction par: André -unknown_user: '&fUtilisateur non enregistré' -unsafe_spawn: '&fTéléportation dans un endroit sûr' -not_logged_in: '&cNon connecté!' -reg_voluntarily: '&fVous venez d''arriver? faites un "/register motdepasse confirmermotdepasse"' +unknown_user: '&fUtilisateur non enregistré.' +unsafe_spawn: '&fTéléportation dans un endroit sûr.' +not_logged_in: '&cNon connecté !' +reg_voluntarily: '&fVous venez d''arriver? Faites un "/register motdepasse confirmermotdepasse"' usage_log: '&cUtilisez: /login motdepasse' -wrong_pwd: '&cMauvais MotdePasse' -unregistered: '&cCe compte a été supprimé!' -reg_disabled: '&cL''enregistrement est désactivé' -valid_session: '&cVous êtes authentifié' -login: '&cConnection effectuée!' -vb_nonActiv: '&fCe compte n''est pas actif, consultez vos emails!' -user_regged: '&cCe nom est deja utilisé.' +wrong_pwd: '&cMauvais mot de passe.' +unregistered: '&cCe compte a été supprimé !' +reg_disabled: '&cL''enregistrement est désactivé.' +valid_session: '&aVous êtes authentifié !' +login: '&aConnexion effectuée !' +vb_nonActiv: '&fCe compte n''est pas actif, consultez vos emails !' +user_regged: '&cCe nom est déjà utilisé.' usage_reg: '&cUtilisez la commande /register motdepasse confirmermotdepasse' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fLimite d''enregistrement atteinte pour ce compte' -no_perm: '&cVous n''avez pas la permission' -password_error_nick: '&fYou can''t use your name as password' -password_error_unsafe: '&fYou can''t use unsafe passwords' -error: '&fUne erreur est apparue, veuillez contacter un administrateur' +no_perm: '&cVous n''avez pas la permission.' +password_error_nick: '&fTu ne peux pas utiliser ton pseudo comme mot de passe.' +password_error_unsafe: '&fCe mot de passe n''est pas accepté, choisis en un autre.' +password_error_chars: '&4Ton mot de passe contient des caractères non autorisés. Permis sont : REG_EX' +error: '&fUne erreur est apparue, veuillez contacter un administrateur.' login_msg: '&cPour vous connecter, utilisez: /login motdepasse' reg_msg: '&cPour vous inscrire, utilisez "/register motdepasse confirmermotdepasse"' reg_email_msg: '&cPour vous inscrire, utilisez "/register "' usage_unreg: '&cPour supprimer ce compte, utilisez: /unregister password' -pwd_changed: '&cMotdePasse changé avec succès!' -user_unknown: '&c Ce compte n''est pas enregistré' -password_error: '&fCe mot de passe est incorrect' -invalid_session: '&fSession invalide, relancez le jeu ou attendez la fin de la session' -reg_only: '&fSeul les joueurs enregistré sont admis! Visite http://example.com' -logged_in: '&cVous êtes déjà connecté!' -logout: '&cVous avez été déconnecté!' +pwd_changed: '&aMot de passe changé avec succès !' +user_unknown: '&cCe compte n''est pas enregistré.' +password_error: '&cCe mot de passe est incorrect.' +invalid_session: '&fSession invalide, relancez le jeu ou attendez la fin de la session.' +reg_only: '&fSeul les joueurs enregistrés sont admis! Visitez http://example.com' +logged_in: '&cVous êtes déjà connecté !' +logout: '&cVous avez été déconnecté !' same_nick: '&fUne personne ayant ce même pseudo joue déjà.' -registered: '&cEnregistrement réussi avec succès!' +registered: '&aEnregistrement réussi avec succès !' pass_len: '&fVotre mot de passe n''est pas assez long.' reload: '&fConfiguration et BDD relancé avec succès' timeout: '&fVous avez été expulsé car vous êtes trop lent pour vous enregistrer !' usage_changepassword: '&fPour changer de mot de passe, utilisez: /changepassword ancienmdp nouveaumdp' -name_len: '&cVotre pseudo est trop long ou trop court' +name_len: '&cVotre pseudo est trop long ou trop court.' regex: '&cCaractères autorisés: REG_EX' add_email: '&cMerci d''ajouter votre email : /email add yourEmail confirmEmail' -recovery_email: '&cVous avez oublié votre MotdePasse? Utilisez /email recovery ' +recovery_email: '&cVous avez oublié votre MotdePasse ? Utilisez /email recovery ' usage_captcha: '&cTrop de tentatives de connexion échouées, utilisez: /captcha ' wrong_captcha: '&cCaptcha incorrect, écrivez de nouveau : /captcha THE_CAPTCHA' -valid_captcha: '&cLe Captcha est valide, merci!' -kick_forvip: '&cUn joueur VIP a rejoint le serveur plein!' -kick_fullserver: '&cLe serveur est actuellement plein, désolé!' +valid_captcha: '&aLe Captcha est valide, merci !' +kick_forvip: '&cUn joueur VIP a rejoint le serveur plein !' +kick_fullserver: '&cLe serveur est actuellement plein, désolé !' usage_email_add: '&fUsage: /email add ' -usage_email_change: '&fUsage: /email change ' +usage_email_change: '&fUsage: /email change ' usage_email_recovery: '&fUsage: /email recovery ' -new_email_invalid: '[AuthMe] Nouvel email invalide!' -old_email_invalid: '[AuthMe] Ancien email invalide!' +new_email_invalid: '[AuthMe] Nouvel email invalide !' +old_email_invalid: '[AuthMe] Ancien email invalide !' email_invalid: '[AuthMe] Email invalide' email_added: '[AuthMe] Email ajouté !' email_confirm: '[AuthMe] Confirmez votre email !' email_changed: '[AuthMe] Email changé !' -email_send: '[AuthMe] Email de récupération envoyé!' -country_banned: 'Votre pays est banni de ce serveur' -antibot_auto_enabled: '[AuthMe] AntiBotMod a été activé automatiquement à cause de nombreuses connections!' -antibot_auto_disabled: '[AuthMe] AntiBotMod a été désactivé automatiquement après %m minutes, espérons que l''invasion soit arrêtée!' +email_send: '[AuthMe] Email de récupération envoyé !' +country_banned: 'Votre pays est banni de ce serveur.' +antibot_auto_enabled: '[AuthMe] AntiBotMod a été activé automatiquement à cause de nombreuses connexions !' +antibot_auto_disabled: '[AuthMe] AntiBotMod a été désactivé automatiquement après %m minutes, espérons que l''invasion soit arrêtée !' kick_antibot: 'AntiBotMod est activé ! Veuillez attendre quelques minutes avant de joindre le serveur.' email_exists: '&cUn email de restauration a déjà été envoyé ! Vous pouvez le jeter et vous en faire envoyez un nouveau en utilisant :' -# TODO two_factor_create: Missing tag %url -two_factor_create: '&2Votre code secret est %code' -# TODO email_already_used: '&4The email address is already being used' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +two_factor_create: '&2Votre code secret est %code. Vous pouvez le scanner depuis %url' +email_already_used: '&4L''adresse email a déjà été utilisée.' +not_owner_error: 'Vous n''êtes pas le propriétaire de ce compte. Veuillez utiliser un autre nom !' +invalid_name_case: 'Veuillez vous connecter avec %valid et non pas avec %invalid.' +denied_chat: 'Vous devez être connecté pour pouvoir parler !' +same_ip_online: 'Un joueur avec la même adresse IP joue déjà !' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index 1562e3a3..01c14292 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -12,6 +12,7 @@ login: '&cIdentificación con éxito!' vb_nonActiv: '&fA túa conta aínda non está activada, comproba a túa bandexa de correo!!' user_regged: '&cEse nome de usuario xa está rexistrado' usage_reg: '&cUso: /register contrasinal confirmarContrasinal' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fExcediches o máximo de rexistros para a túa Conta' no_perm: '&cNon tes o permiso' error: '&fOcurriu un erro; contacta cun administrador' @@ -55,11 +56,15 @@ country_banned: 'O teu país está bloqueado neste servidor' antibot_auto_enabled: '[AuthMe] AntiBotMod conectouse automáticamente debido a conexións masivas!' antibot_auto_disabled: '[AuthMe] AntiBotMod desactivouse automáticamente despois de %m minutos, esperemos que a invasión se detivera' -# TODO email_already_used: '&4The email address is already being used' # TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index 7aa03b39..7dd9cca1 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -16,34 +16,34 @@ password_error_unsafe: '&cA választott jelszó nem biztonságos, kérlek válas unregistered: '&cRegisztráció sikeresen törölve!' same_nick: 'Ezzel a játékosnévvel már játszanak a szerveren.' valid_session: '&2A megadott időkereten belül csatlakoztál vissza így a rendszer automatikusan beléptetett.' -pwd_changed: '&cJelszó cserélve!' -reload: 'Beálítások és adatbázis újratöltve!' +pwd_changed: '&cJelszó sikeresen megváltoztatva!' +reload: 'Beállítások és adatbázis újratöltve!' timeout: 'Bejelentkezési időtúllépés!' -error: 'Hiba lépett fel! Lépj kapcsolatba a tulajjal!' +error: 'Hiba lépett fel! Lépj kapcsolatba a szerver tulajával sürgősen!' logged_in: '&cMár be vagy jelentkezve!' login: '&aSikeresen beléptél!' wrong_pwd: '&4Hibás jelszó!' user_unknown: '&cEz a felhasználó nincs regisztrálva!' reg_msg: '&cKérlek Regisztrálj: "/register "' -reg_email_msg: '&cKérlek regisztrálj: "/register "' -unsafe_spawn: 'A kilépési helyzeted nem biztonságos, teleportálás a kezdő pozícióra.' +reg_email_msg: '&cKérlek regisztrálj: "/register "' +unsafe_spawn: 'A kilépési helyzeted nem biztonságos, ezért elteleportálunk a kezdő pozícióra.' max_reg: '&cElérted a maximálisan beregisztrálható karakterek számát. (%reg_count/%max_acc %reg_names)!' password_error: 'A két jelszó nem egyezik!' invalid_session: '&cAz IP címed megváltozott, ezért a visszacsatlakozási időkereted lejárt.' pass_len: 'A jelszavad nem éri el a minimális hosszúságot!' vb_nonActiv: '&cA felhasználód aktiválása még nem történt meg, ellenőrizd a megadott emailed!' -usage_changepassword: 'Használat: "/changepassword <új Jelszó>"' -name_len: '&4A felhasználó neved túl hosszú, vagy túl rövid! Válassz másikat!' -regex: '&4A felhasználóneved nem használható karaktereket tartalmaz. Elfogadott karakterek: REG_EX' -add_email: '&3Kérlek add hozzá a felhasználódhoz az email címedet "/email add "' -recovery_email: '&3Ha elfelejtetted a jelszavad, használd az: "/email recovery "' +usage_changepassword: 'Használat: "/changepassword <új jelszó>"' +name_len: '&4A felhasználóneved túl hosszú, vagy túl rövid! Válassz másikat!' +regex: '&4A felhasználóneved nem engedélyezett karaktereket tartalmaz. Engedélyezett karakterek: REG_EX' +add_email: '&3Kérlek rendeld hozzá a felhasználódhoz az email címedet "/email add "' +recovery_email: '&3Ha elfelejtetted a jelszavad, használd az: "/email recovery "' usage_captcha: '&3A bejelentkezéshez CAPTCHA szükséges, kérlek használd a következő parancsot "/captcha "' -wrong_captcha: '&cHibás CAPTCHA, kérlek írd be a következő parancsot "/captcha THE_CAPTCHA"!' +wrong_captcha: '&cHibás CAPTCHA, kérlek írd be a következő parancsot: "/captcha THE_CAPTCHA"!' valid_captcha: '&2CAPTCHA sikeresen feloldva!' kick_forvip: '&3VIP játékos csatlakozott a szerverhez!' kick_fullserver: '&4A szerver megtelt, próbálj csatlakozni később!' -usage_email_add: '&cHasználat: "/email add "' -usage_email_change: '&cHasználat: "/email change <új Email>"' +usage_email_add: '&cHasználat: "/email add "' +usage_email_change: '&cHasználat: "/email change <új email>"' usage_email_recovery: '&cHasználat: "/email recovery "' new_email_invalid: '&cHibás az új email cím, próbáld újra!' old_email_invalid: '&cHibás a régi email cím, próbáld újra!' @@ -61,3 +61,8 @@ kick_antibot: 'Az AntiBot védelem bekapcsolva! Kérlek várj pár percet mielő not_owner_error: 'Ez nem a te felhasználód. Kérlek válassz másik nevet!' invalid_name_case: '%valid a felhasználó neved nem? Akkor ne %invalid névvel próbálj feljönni.' two_factor_create: '&2A te titkos kódod a következő: %code. Vagy skenneld be a következő oldalról: %url' +denied_chat: '&cAmíg nem vagy bejelentkezve, nem használhatod a csevegőt!' +denied_command: '&cAmíg nem vagy bejelentkezve, nem használhatod ezt a parancsot!' +same_ip_online: 'Már valaki csatlakozott a szerverhez ezzel az IP címmel!' +password_error_chars: '&4A választott jelszó nem engedélyezett karaktereket tartalmaz. Engedélyezett karakterek: REG_EX' +tempban_max_logins: '&cIdeiglenesen ki lettél tiltva mert túl sok alkalommal rontottad el a jelszavad!' diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index 24e6a35b..88b45933 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -10,6 +10,7 @@ valid_session: '&2Otomatis login, karena sesi masih terhubung.' login: '&2Login berhasil!' vb_nonActiv: '&cAkunmu belum diaktifkan, silahkan periksa email kamu!' user_regged: '&cKamu telah mendaftarkan username ini!' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&Kamu telah mencapai batas maksimum pendaftaran di server ini!' no_perm: '&4Kamu tidak mempunyai izin melakukan ini!' error: '&4Terjadi kesalahan tak dikenal, silahkan hubungi Administrator!' @@ -53,11 +54,15 @@ email_send: '&2Email pemulihan akun telah dikirim! Silahkan periksa kotak masuk email_exists: '&cEmail pemulihan sudah dikirim! kamu bisa membatalkan dan mengirimkan yg baru dengan command dibawah:' antibot_auto_enabled: '&4[AntiBotService] AntiBot diaktifkan dikarenakan banyak koneksi yg diterima!' antibot_auto_disabled: '&2[AntiBotService] AntiBot dimatikan setelah %m menit!' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO email_already_used: '&4The email address is already being used' -# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO country_banned: '&4Your country is banned from this server!' # TODO usage_unreg: '&cUsage: /unregister ' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' -# TODO usage_reg: '&cUsage: /register ' \ No newline at end of file +# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' +# TODO usage_reg: '&cUsage: /register ' +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index 6361a4c9..e4b9b7ae 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -25,6 +25,7 @@ user_unknown: '&cL''utente non ha ancora eseguito la registrazione.' password_error: '&cLe password non corrispondono!' password_error_nick: '&cNon puoi usare il tuo nome utente come password, per favore scegline un''altra...' password_error_unsafe: '&cLa password che hai inserito non è sicura, per favore scegline un''altra...' +password_error_chars: '&4La tua password contiene caratteri non consentiti. I caratteri consentiti sono: REG_EX' invalid_session: '&cIl tuo indirizzo IP è cambiato e la tua sessione è stata terminata!' reg_only: '&4Puoi giocare in questo server solo dopo aver effettuato la registrazione attraverso il sito web! Per favore, vai su http://esempio.it per procedere!' logged_in: '&cHai già eseguito l''autenticazione, non è necessario eseguirla nuovamente!' @@ -62,3 +63,6 @@ email_already_used: '&4L''indirizzo email inserito è già in uso' two_factor_create: '&2Il tuo codice segreto è: &f%code&n&2Puoi anche scannerizzare il codice QR da qui: &f%url' not_owner_error: 'Non sei il proprietario di questo account. Per favore scegli un altro nome!' invalid_name_case: 'Dovresti entrare con questo nome utente: "%valid", al posto di: "%invalid".' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index ab122309..17dd47c9 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -14,6 +14,7 @@ login: '&c성공적인 접속입니다!' vb_nonActiv: '&f당신의 계정은 아직 활성화되어있지 않습니다, 당신의 이메일을 확인해보세요!' user_regged: '&c사용자이름은 이미 가입했습니다' usage_reg: '&c사용법: /register 비밀번호 비밀번호확인' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&f당신은 가입할 수 있는 계정의 최대 한도를 초과했습니다' no_perm: '&c권한이 없습니다' error: '&f오류가 발생했습니다; 관리자에게 문의해주세요' @@ -57,10 +58,14 @@ email_exists: '[AuthMe] 당신의 계정에 이미 이메일이 존재합니다. country_banned: '당신의 국가는 이 서버에서 차단당했습니다' antibot_auto_enabled: '[AuthMe] 봇차단모드가 연결 개수 때문에 자동적으로 활성화됩니다!' antibot_auto_disabled: '[AuthMe] 봇차단모드가 %m 분 후에 자동적으로 비활성화됩니다' -# TODO email_already_used: '&4The email address is already being used' # TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index fce26d1b..7c06429f 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -11,6 +11,7 @@ login: '&aSekmingai prisijungete' vb_nonActiv: '&aJusu vartotojas nera patvirtintas, patikrinkite el.pasta.' user_regged: '&cVartotojo vardas jau uzregistruotas' usage_reg: '&eNaudojimas: /register slaptazodis pakartotiSlaptazodi' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&cJus pasiekete maksimalu registraciju skaiciu.' no_perm: '&cNera leidimo' error: '&cAtsirado klaida, praneskite adminstratoriui.' @@ -40,24 +41,28 @@ wrong_captcha: '&cNeteisinga Captcha, naudokite : /captcha THE_CAPTCHA' valid_captcha: '&cJusu captcha Teisinga!' kick_forvip: '&cA VIP prisijunge i pilna serveri!' kick_fullserver: '&cServeris yra pilnas, Atsiprasome.' -# TODO email_already_used: '&4The email address is already being used' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' -# TODO usage_email_change: '&cUsage: /email change ' -# TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' -# TODO new_email_invalid: '&cInvalid new email, try again!' -# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO email_send: '&2Recovery email sent successfully! Please check your email inbox!' -# TODO usage_email_recovery: '&cUsage: /email recovery ' -# TODO email_confirm: '&cPlease confirm your email address!' -# TODO old_email_invalid: '&cInvalid old email, try again!' -# TODO email_changed: '&2Email address changed correctly!' -# TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' -# TODO email_added: '&2Email address successfully added to your account!' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO country_banned: '&4Your country is banned from this server!' -# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' -# TODO usage_email_add: '&cUsage: /email add ' -# TODO email_invalid: '&cInvalid email address, try again!' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO email_changed: '&2Email address changed correctly!' +# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' +# TODO new_email_invalid: '&cInvalid new email, try again!' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' +# TODO usage_email_recovery: '&cUsage: /email recovery ' +# TODO country_banned: '&4Your country is banned from this server!' +# TODO usage_email_add: '&cUsage: /email add ' +# TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' +# TODO email_invalid: '&cInvalid email address, try again!' +# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' +# TODO email_added: '&2Email address successfully added to your account!' +# TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' +# TODO email_send: '&2Recovery email sent successfully! Please check your email inbox!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO usage_email_change: '&cUsage: /email change ' +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO email_confirm: '&cPlease confirm your email address!' +# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' +# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO old_email_invalid: '&cInvalid old email, try again!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index a3159e48..db7a9baf 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -13,6 +13,7 @@ password_error_unsafe: '&fJe kunt geen onveilige wachtwoorden gebruiken' vb_nonActiv: Je accound is nog niet geactiveerd, controleer je mailbox! user_regged: '&cGebruikersnaam is al geregistreerd' usage_reg: '&cGebruik: /register ' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: Je hebt de maximale registraties van jouw account overschreden. no_perm: '&cGeen toegang!' error: 'Error: neem contact op met een administrator!' @@ -22,6 +23,7 @@ usage_unreg: '&cGebruik: /unregister password' pwd_changed: '&cWachtwoord aangepast!' user_unknown: '&cGebruikersnaam niet geregistreerd' password_error: Wachtwoord incorrect! +password_error_chars: '&cJouw wachtwoord bevat illegale tekens. Toegestaane karakters: REG_EX' invalid_session: Sessie beschadigd, wacht tot de sessie is verlopen en verbindt opnieuw. reg_only: Alleen voor geregistreerde spelers! Bezoek http://example.com om te registreren logged_in: '&cJe bent al ingelogd!' @@ -57,8 +59,11 @@ antibot_auto_disabled: '[AuthMe] AntiBotMod automatisch uitgezet na %m minuten, kick_antibot: 'AntiBot is aangezet! Wacht alsjeblieft enkele minuten voor je met de server verbindt.' # TODO two_factor_create: Missing tag %url two_factor_create: '&2Je geheime code is %code' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' # TODO email_already_used: '&4The email address is already being used' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' # TODO reg_email_msg: '&3Please, register to the server with the command "/register "' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO denied_chat: '&cIn order to chat you must be authenticated!' +# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index c6cba56f..7630ca94 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -26,6 +26,7 @@ error: '&fBlad prosimy napisac do aministracji' unknown_user: '&fUzytkownika nie ma w bazie danych' unsafe_spawn: '&fTwoje pozycja jest niebezpieczna. Zostaniesz przeniesiony na bezpieczny spawn.' invalid_session: '&fSesja zakonczona!' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fPrzekroczyles limit zarejestrowanych kont na serwerze.' password_error: '&fHaslo niepoprawne!' pass_len: '&fTwoje haslo jest za krotkie lub za dlugie! Sprobuj ponownie...' @@ -50,14 +51,18 @@ email_added: '[AuthMe] Email dodany!' email_confirm: '[AuthMe] Potwierdz swoj email!' email_changed: '[AuthMe] Email zmieniony!' email_send: '[AuthMe] Email z odzyskaniem wyslany!' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' # TODO email_already_used: '&4The email address is already being used' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' +# TODO country_banned: '&4Your country is banned from this server!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO country_banned: '&4Your country is banned from this server!' -# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' +# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' # TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index 64428a52..d35ab6b9 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -11,6 +11,7 @@ login: '&cAutenticado com sucesso!' vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. ' user_regged: '&cUtilizador já registado' usage_reg: '&cUse: /register seu@email.com seu@email.com' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&cAtingiu o numero máximo de registos permitidos' no_perm: '&cSem Permissões' error: '&fOcorreu um erro; Por favor contacte um admin' @@ -54,11 +55,15 @@ email_send: 'Nova palavra-passe enviada para o seu email!' country_banned: 'O seu país está banido deste servidor' antibot_auto_enabled: '[AuthMe] AntiBotMod activado automaticamente devido a um aumento anormal de tentativas de ligação!' antibot_auto_disabled: '[AuthMe] AntiBotMod desactivado automaticamente após %m minutos, esperamos que a invasão tenha parado' -# TODO email_already_used: '&4The email address is already being used' # TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 2b9dd640..b615d868 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -11,6 +11,7 @@ login: '&a&lВы успешно вошли!' vb_nonActiv: '&6Ваш аккаунт еще не активирован! Проверьте вашу почту!' user_regged: '&c&lТакой игрок уже зарегистрирован' usage_reg: '&c&lИспользование: &e&l/reg ПАРОЛЬ ПОВТОР_ПАРОЛЯ' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&c&lВы превысили макс количество регистраций на ваш IP' no_perm: '&c&lНедостаточно прав' error: '&c&lПроизошла ошибка. Свяжитесь с администратором' @@ -18,6 +19,7 @@ login_msg: '&a&lАвторизация: &e&l/login ПАРОЛЬ' reg_msg: '&a&lРегистрация: &e&l/reg ПАРОЛЬ ПОВТОР_ПАРОЛЯ' password_error_nick: '&c&lВы не можете использовать ваш ник в роли пароля' password_error_unsafe: '&c&lВы не можете использовать небезопасный пароль' +regex: '&c&lВаш пароль содержит запрещенные символы. Разрешенные символы: REG_EX' reg_email_msg: '&c&lРегистрация: &e&l/reg EMAIL ПОВТОР_EMAIL' usage_unreg: '&c&lИспользование: &e&l/unregister ПАРОЛЬ' pwd_changed: '&2Пароль изменен!' @@ -55,9 +57,13 @@ email_send: '[AuthMe] Письмо с инструкциями для восст country_banned: 'Вход с IP-адресов вашей страны воспрещен на этом сервере' antibot_auto_enabled: '&a[AuthMe] AntiBot-режим автоматически включен из-за большого количества входов!' antibot_auto_disabled: '&a[AuthMe] AntiBot-режим автоматичски отключен после %m мин. Надеюсь атака закончилась' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index 77433f62..56adb408 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -29,6 +29,7 @@ error: '&fNastala chyba; Kontaktujte administrátora' unknown_user: '&fHrac nie je v databázi' unsafe_spawn: '&fTvoj pozícia bol nebezpecná, teleportujem hraca na spawn' invalid_session: '&fZapamätane casove data nie su doveryhodne. Cakaj na ukoncenie spojenia' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fDosiahol si maximum registrovanych uctov.' password_error: '&fHeslá sa nezhodujú' pass_len: '&fHeslo je velmi kratke alebo dlhe' @@ -38,29 +39,33 @@ name_len: '&cTvoje meno je velmi krátke alebo dlhé' regex: '&cTvoje meno obsahuje zakázané znaky. Povolené znaky: REG_EX' add_email: '&cPridaj svoj e-mail príkazom "/email add email zopakujEmail"' recovery_email: '&cZabudol si heslo? Pouzi príkaz /email recovery ' -# TODO usage_email_change: '&cUsage: /email change ' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' -# TODO email_already_used: '&4The email address is already being used' -# TODO new_email_invalid: '&cInvalid new email, try again!' -# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -# TODO old_email_invalid: '&cInvalid old email, try again!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO email_changed: '&2Email address changed correctly!' -# TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' +# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' +# TODO new_email_invalid: '&cInvalid new email, try again!' +# TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' +# TODO kick_fullserver: '&4The server is full, try again later!' +# TODO usage_email_recovery: '&cUsage: /email recovery ' # TODO country_banned: '&4Your country is banned from this server!' # TODO usage_email_add: '&cUsage: /email add ' -# TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' -# TODO valid_captcha: '&2Captcha code solved correctly!' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO email_send: '&2Recovery email sent successfully! Please check your email inbox!' -# TODO usage_email_recovery: '&cUsage: /email recovery ' -# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' -# TODO email_confirm: '&cPlease confirm your email address!' -# TODO kick_fullserver: '&4The server is full, try again later!' -# TODO email_added: '&2Email address successfully added to your account!' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO kick_forvip: '&3A VIP player has joined the server when it was full!' # TODO email_invalid: '&cInvalid email address, try again!' -# TODO antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' +# TODO email_added: '&2Email address successfully added to your account!' +# TODO antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled disabled after %m minutes!' +# TODO email_send: '&2Recovery email sent successfully! Please check your email inbox!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO kick_forvip: '&3A VIP player has joined the server when it was full!' +# TODO usage_email_change: '&cUsage: /email change ' +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO email_confirm: '&cPlease confirm your email address!' +# TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' +# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' +# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' +# TODO old_email_invalid: '&cInvalid old email, try again!' +# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' +# TODO valid_captcha: '&2Captcha code solved correctly!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 0e118de0..2f1992ef 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -1,63 +1,68 @@ -unknown_user: '&fKullanici veritabanina ekli degil' -unsafe_spawn: '&fDogdugunuz konum guvenli degildi, lobiye isinlaniyorsunuz...' -not_logged_in: '&cGiris Yapmadin!' -reg_voluntarily: '&fKullanici adinla kayit olabilirsin! Komut: "/register sifren sifrentekrar"' -usage_log: '&cKullanimi: /login sifren' -wrong_pwd: '&cYanlis sifre' -unregistered: '&cSunucudan kaydiniz basariyla silindi!' -reg_disabled: '&cKayit deaktif' -valid_session: '&cOturum Acma' -login: '&cBasarili giris!' -vb_nonActiv: '&fHesabin aktiflestirilmedi! Emailini kontrol et' -user_regged: '&cKullanici zaten oyunda' -usage_reg: '&cKullanimi: /register sifre sifretekrar' -max_reg: '&fMaximim kayit limitini astin!' -no_perm: '&cYetkin yok' -error: '&fBir hata olustu; Lutfen adminle iletisime gec' -login_msg: '&cGiris Yapin : "/login sifre"' -reg_msg: '&cLutfen kaydolmak icin : "/register sifre sifretekrar"' -reg_email_msg: '&cLutfen Kaydolmak icin : "/register "' -usage_unreg: '&cKullanimi: /unregister sifren' -pwd_changed: '&cSifreniz degisti!' -user_unknown: '&cBu kullaniciyla kaydolunmamis!' -password_error: '&fSifren eslesmiyor' -invalid_session: '&fOturum veritabanlari uyusmuyor lutfen sonunu bekleyin' -reg_only: '&fSadece kayitli uyeler girebilir ! Kayit olmak icin www.orneksite.com adresini ziyaret ediniz !' -logged_in: '&cZaten Giris Yapilmis!' -logout: '&cBasarili cikis' -same_nick: '&fAyni kullanici oyunda' -registered: '&cBasarili kayit!' -pass_len: '&fSifren cok uzun ya da kisa olmamali ' -reload: '&fKonfigurasyon dosyasi ve veritabani yüklendi' -timeout: '&fZaman Asimi' -usage_changepassword: '&fkullanimi: /changepassword eskisifre yenisifre' -name_len: '&cKullanici adin cok kisa ya da cok uzun' -regex: '&cKullanici adin ozel karakterler iceriyor. Uygun karakterler: REG_EX' -add_email: '&cLutfen emailini ekle : /email add ' -recovery_email: '&cSifreni mi unuttun? Degistirmek icin : /email recovery ' -usage_captcha: '&cBir captcha yazman lazim , yazmak icin: /captcha ' -wrong_captcha: '&cYanlis Captcha, kullanmak icin : /captcha THE_CAPTCHA' -valid_captcha: '&cCaptcha gecerli !' -kick_forvip: '&cSenin yerine bir VIP kullanıcı girdi!' -kick_fullserver: '&cServer suanda dolu gozukuyor, Uzgunum!' -usage_email_add: '&fKullanimi: /email add ' -usage_email_change: '&fKullanimi: /email change ' -usage_email_recovery: '&fKullanimi: /email recovery ' -new_email_invalid: '[AuthMe] Yeni eposta gecersiz!' -old_email_invalid: '[AuthMe] Eski eposta gecersiz!' -email_invalid: '[AuthMe] Gecersiz Eposta' -email_added: '[AuthMe] Eposta Eklendi !' -email_confirm: '[AuthMe] Epostani Dogrula !' -email_changed: '[AuthMe] Eposta Degistirildi !' -email_send: '[AuthMe] Kurtarma postasi gonderildi !' -country_banned: 'Ulken bu serverdan banlandi !' -antibot_auto_enabled: '[AuthMe] AntiBotMode otomatik olarak etkinlestirildi!' -antibot_auto_disabled: '[AuthMe] AntiBotMode %m dakika sonra otomatik olarak isgal yuzundan devredisi birakildi' -# TODO email_already_used: '&4The email address is already being used' -# 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 kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +kick_antibot: 'AntiBot koruma modu aktif! Birkac dakika sonra tekrar girmeyi deneyin.' +unknown_user: '&cVeritabaninda kullanici bulunamadi!' +unsafe_spawn: '&cOyundan ciktigin yer guvenli degil, baslangic noktasina isinlaniyorsun.' +not_logged_in: '&cGiris yapmadin!' +reg_voluntarily: 'Kendini sunucuya kaydetmek icin komut kullan, "/register "' +usage_log: '&cKullanim: /login ' +wrong_pwd: '&cYanlis sifre!' +unregistered: '&cKayit basariyla kaldirildi!' +reg_disabled: '&cOyun icin kayit olma kapatildi!' +valid_session: '&2Oturum icin yeniden giris gerekiyor.' +login: '&2Giris basarili!' +vb_nonActiv: '&cHeabiniz henuz aktif edilmemis, e-postanizi kontrol edin!' +user_regged: '&cSenin adinda daha once birisi kaydolmus!' +usage_reg: '&cKullanim: /register ' +max_reg: '&cSen maksimum kayit sinirini astin (%reg_count/%max_acc %reg_names)!' +no_perm: '&4Bunu yapmak icin iznin yok!' +error: '&4Beklenmedik bir hata olustu, yetkili ile iletisime gecin!' +login_msg: '&cLutfen giris komutunu kullanin "/login "' +reg_msg: '&3Lutfen kayit komutunu kullanin "/register "' +reg_email_msg: '&3Lutfen kayit komutunu kullanin "/register "' +usage_unreg: '&cKullanim: /unregister ' +pwd_changed: '&2Sifre basariyla degistirildi!' +user_unknown: '&cBu oyuncu kayitli degil!' +password_error: '&cSifre eslesmiyor, tekrar deneyin!' +password_error_nick: '&cSifrenize adinizi koyamazsiniz, lutfen farkli bir sifre secin...' +password_error_unsafe: '&cSectiginiz sifre guvenli degil, lutfen farkli bir sifre secin...' +invalid_session: '&cIP adresin degistirildi ve oturum suren doldu!' +reg_only: '&4Sunucuya kayit sadece internet uzerinden yapilmakta! Lutfen http://ornek.com sitesini kayit icin ziyaret edin!' +logged_in: '&cZaten giris yaptin!' +logout: '&2Basariyla cikis yaptin!' +same_nick: '&4Senin isminde bir oyuncu suncuda bulunmakta!' +registered: '&2Basariyla kaydoldun!' +pass_len: '&cSenin sifren ya cok kisa yada cok uzun! Lutfen farkli birsey dene!' +reload: '&2Ayarlar ve veritabani yenilendi!' +timeout: '&4Giris izni icin verilen zaman suresini astigin icin sunucudan atildin, tekrar deneyin!' +usage_changepassword: '&cKullanim: /changepassword ' +name_len: '&4Senin ismin ya cok kisa yada cok uzun!' +regex: '&4Senin isminde uygunsuz karakterler bulunmakta. Izin verilen karakterler: REG_EX' +add_email: '&3Lutfen hesabinize eposta adresinizi komut ile ekleyin "/email add "' +recovery_email: '&3Sifreni mi unuttun ? Komut kullanarak ogrenebilirsin "/email recovery "' +# TODO usage_captcha: Missing tag +usage_captcha: '&3Giris yapmak icin guvenlik kodunu komut yazarak girin "/captcha "' +wrong_captcha: '&cYanlis guvenlik kodu, kullanim sekli "/captcha THE_CAPTCHA" sohbete yazin!' +valid_captcha: '&2Guvenlik kodu dogrulandi!' +kick_forvip: '&3Bir VIP oyuna giris yaptigi icin atildin!' +kick_fullserver: '&4Sunucu suanda dolu, daha sonra tekrar deneyin!' +usage_email_add: '&cKullanim: /email add ' +usage_email_change: '&cKullanim: /email change ' +usage_email_recovery: '&cKullanim: /email recovery ' +new_email_invalid: '&cGecersiz yeni eposta, tekrar deneyin!' +old_email_invalid: '&cGecersiz eski eposta, tekrar deneyin!' +email_invalid: '&cGecersiz eposta, tekrar deneyin!' +email_added: '&2Eposta basariyla kullaniciniza eklendi!' +email_confirm: '&cLutfen tekrar epostanizi giriniz!' +email_changed: '&2Epostaniz basariyla degistirildi!' +email_send: '&2Sifreniz epostaniza gonderildi! Lutfen eposta kutunuzu kontrol edin!' +email_exists: '&cSifreniz zaten epostanize gonderildi! Bunu iptal etmek veya yeni bir sifre gondermek icin assagidaki komutu kullanabilirsin:' +country_banned: '&4Senin bolgen sunucudan yasaklandi!' +antibot_auto_enabled: '&4[AntiBotServis] Saldiri oldugu icin AntiBot aktif edildi!' +antibot_auto_disabled: '&2[AntiBotServis] AntiBot, %m dakika sonra deaktif edilecek!' +email_already_used: '&4Eposta adresi zaten kullaniliyor.' +two_factor_create: '&2Gizli kodunuz %code. Buradan test edebilirsin, %url' +not_owner_error: 'Bu hesabin sahibi degilsin. Lutfen farkli bir isim sec!' +invalid_name_case: 'Oyuna %valid isminde katilmalisin. %invalid ismini kullanarak katilamazsin.' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index df5710f9..6c84e72a 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -11,6 +11,7 @@ login: '&2Успішна авторизація!' vb_nonActiv: '&fВаш акаунт не активований. Перевірте свою електронну адресу!' user_regged: '&cТакий користувач вже зареєстрований' usage_reg: '&cВикористовуйте: /reg Пароль Повтор_Пароля' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&fВи перевищили максимальне число реєстрацій на ваш IP' no_perm: '&cУ Вас недостатньо прав' error: '&fЩось пішло не так; Будь ласка зв`яжіться з адміністратором' @@ -56,9 +57,13 @@ country_banned: 'Сервер не доступний для вашої краї antibot_auto_enabled: '[AuthMe] AntiBotMod автоматично увімкнений (забагато одначасних з`єднань)!' # TODO antibot_auto_disabled: Missing tag %m antibot_auto_disabled: '[AuthMe] AntiBotMod автоматично вимкнувся, сподіваємось атака зупинена' +# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' +# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO email_already_used: '&4The email address is already being used' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' # TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' # TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' \ No newline at end of file diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index 262b7b73..a3939aee 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -1,64 +1,63 @@ -unknown_user: '&fNgười chơi không tồn tại trong cơ sở dữ liệu' -unsafe_spawn: '&fNơi thoát server của bạn không an toàn, đang dịch chuyển bạn tới điểm spawn của server' -not_logged_in: '&cChưa đăng nhập!' -reg_voluntarily: '&fBạn có thể đăng kí tài khoản với lệnh "/register mật-khẩu nhập-lại-mật-khẩu"' -usage_log: '&eSử dụng: /login password' -wrong_pwd: '&cSai mật khẩu' -unregistered: '&cHuỷ đăng kí thành công!' -reg_disabled: '&cHệ thống đăng kí đã bị vô hiệu' -valid_session: '&cPhiên đăng nhập còn tồn tại, bạn không cần nhập mật khẩu' -login: '&cĐăng nhập thành công!' -vb_nonActiv: '&fTài khoản của bạn chưa được kích hoạt, kiểm tra email!' -user_regged: '&cTên đăng nhập này đã được đăng kí' -usage_reg: '&eSử dụng: /register mật-khẩu nhập-lại-mật-khẩu' -max_reg: '&fSố lượng tài khoản ở IP của bạn trong server này đã quá giới hạn cho phép' -no_perm: '&cKhông có quyền' -error: '&fCó lỗi xảy ra; Báo lại cho người điều hành server' -login_msg: '&cĐăng nhập với lệnh "/login mật-khẩu"' -reg_msg: '&cĐăng kí tài khoản với lệnh "/register mật-khẩu nhập-lại-mật-khẩu"' -reg_email_msg: '&cĐăng kí email cho tài khoản với lệnh "/register "' -usage_unreg: '&eSử dụng: /unregister mật-khẩu' -pwd_changed: '&cĐã đổi mật khẩu!' -user_unknown: '&cTài khoản này chưa được đăng kí' -password_error: '&fMật khẩu không khớp' -invalid_session: '&fPhiên đăng nhập không hồi đáp, vui lòng chờ phiên đăng nhập kết thúc' -reg_only: '&fChỉ cho phép người đã đăng kí! Hãy vào trang http://web-của.bạn/ để đăng kí' -logged_in: '&cĐã đăng nhập!' -logout: '&cThoát đăng nhập thành công' -same_nick: '&fTài khoản đang được người khác sử dụng trong server' -registered: '&cĐăng kí thành công!' -pass_len: '&fMật khẩu của bạn quá ngắn hoặc quá dài' -reload: '&fThiết lập và dữ liệu đã được nạp lại' -timeout: '&fQuá thời gian đăng nhập' -usage_changepassword: '&eSử dụng: /changepassword mật-khẩu-cũ mật-khẩu-mới' -name_len: '&cTên đăng nhập của bạn quá ngắn hoặc quá dài' -regex: '&cTên đăng nhập của bạn có chứa kí tự đặc biệt không được cho phép. Các kí tự hợp lệ: REG_EX' -add_email: '&cVui lòng thêm địa chỉ email cho tài khoản với lệnh: /email add email-của-bạn nhập-lại-email-của-bạn' -recovery_email: '&cQuên mật khẩu? Hãy dùng lệnh /email recovery ' -usage_captcha: '&cBạn cần nhập mã xác nhận: /captcha ' -# TODO wrong_captcha: Missing tag THE_CAPTCHA -wrong_captcha: '&cSai mã xác nhận, nhập lại: /captcha ' -valid_captcha: '&aMã xác nhận hợp lệ!' -kick_forvip: '&cNgười chơi VIP đã vào server hiện đang full!' -kick_fullserver: '&cXin lỗi, hiện tại server không còn trống slot để bạn có thể vào!' -usage_email_add: '&eSử dụng: /email add ' -usage_email_change: '&eSử dụng: /email change ' -usage_email_recovery: '&eSử dụng: /email recovery ' -new_email_invalid: '[AuthMe] Địa chỉ email mới không hợp lệ!' -old_email_invalid: '[AuthMe] Địa chỉ email cũ không hợp lệ!' -email_invalid: '[AuthMe] Sai địa chỉ email' -email_added: '[AuthMe] Đã thêm địa chỉ email !' -email_confirm: '[AuthMe] Xác nhận email !' -email_changed: '[AuthMe] Đã thay đổi email !' -email_send: '[AuthMe] Đã gửi email khôi phục mật khẩu tới bạn !' -country_banned: 'Rất tiếc, quốc gia của bạn không được phép gia nhập server' -antibot_auto_enabled: '[AuthMe] AntiBot đã được kích hoạt vì lượng người chơi kết nối vượt quá giới hạn!' -antibot_auto_disabled: '[AuthMe] AntiBot tự huỷ kích hoạt sau %m phút, hi vọng lượng kết nối sẽ giảm bớt' -# TODO email_already_used: '&4The email address is already being used' -# 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 kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO not_owner_error: 'You are not the owner of this account. Please try another name!' \ No newline at end of file +kick_antibot: 'Chế độ AntiBot đã được kích hoạt! Bạn phải đợi vài phút trước khi tham gia vào máy chủ.' +unknown_user: '&cTài khoảng không có cơ sở dử liệu' +unsafe_spawn: '&cBạn đang ở vị trí không an toàn, bạn đã được dịch chuyển về Spawn.' +not_logged_in: '&cBạn chưa đăng nhập!' +reg_voluntarily: 'Bạn cần đăng ký tài khoảng để tham gia máy chủ với lệnh "/register "' +usage_log: '&cSử dụng: /login ' +wrong_pwd: '&cSai mật khẩu!' +unregistered: '&cHủy đăng ký thành công!' +reg_disabled: '&cKhông cho phép đăng ký tài khoảng trong máy chủ!' +valid_session: '&2Phiên đăng nhập đã được kết nối trở lại.' +login: '&2Đăng nhập thành công!' +vb_nonActiv: '&cTài khoảng của bạn chưa được kích hoạt, vui lòng kiễm tra email!' +user_regged: '&cBạn có thể đăng ký với tên tài khoảng này!' +usage_reg: '&cSử dụng: /register ' +max_reg: '&cBạn đã vượt quá giới hạn tối đa đăng ký tài khoảng (%reg_count/%max_acc %reg_names) cho những lần kết nối tài khoảng!' +no_perm: '&4Bạn không có quyền truy cập lệnh này!' +error: '&4Lỗi! Vio lòng liên hệ quản trị viên hoặc admin' +login_msg: '&cXin vui lòng đăng nhập bằng lệnh "/login "' +reg_msg: '&2Xin vui lòng đăng ký tài khoảng với lệnh "/register "' +reg_email_msg: '&2Sử dụng email để đăng ký tham gia máy chủ với lệnh "/register "' +usage_unreg: '&cSử dụng: /unregister ' +pwd_changed: '&2Thay đỗi mật khẩu thành công!' +user_unknown: '&cNgười dùng này đã được đăng ký!' +password_error: '&cMật khẩu không hợp, xin vui lòng kiễm tra lại!' +password_error_nick: '&cBạn không thể đặt mật khẩu bằng tên của mình, vui lòng đặt lại...' +password_error_unsafe: '&cMật khẩu của bạn vừa đặt không an toàn, vui lòng đặt lại...' +invalid_session: '&cIP của bạn đã bị thay đổi và phiên đăng nhập của bạn đã hết hạn!' +reg_only: '&4Chỉ có thành viên mới có thể tham gia máy chủ, vui lòng truy cập trang web http://kythuat.mobi/forum/ để đăng ký thành viên!' +logged_in: '&cBạn đã đăng nhập!' +logout: '&2Bạn đã đăng xuất!' +same_nick: '&4Tài khoảng đang được sử dụng trên máy chủ!' +registered: '&2Đăng ký thành công!' +pass_len: '&cMật khẩu của bạn đặt quá dài hoặc quá ngắn, vui lòng đặt lại!' +reload: '&2Cấu hình và cơ sở dử liệu đã được nạp lại!' +timeout: '&4Thời gian đăng nhập đã hết, bạn đã bị văng khỏi máy chủ. Xin vui lòng thử lại!' +usage_changepassword: '&cSử dụng: /changepassword ' +name_len: '&4Tên đăng nhập của bạn quá ngắn hoặc quá dài!' +regex: '&4Tên nhân vật có chứa ký tự không hợp lệ hoặc chữ Hoa, vui lòng đặt lại chử thường, số hoặc _' +add_email: '&eVui lòng thêm email của bạn với lệnh "/email add "' +recovery_email: '&aBạn quên mật khẩu? Vui lòng gõ lệnh "/email recovery "' +usage_captcha: '&eĐể đăng nhập vui lòng hãy gõ mã Captcha, nhấn lệnh "/captcha "' +wrong_captcha: '&cSai mã captcha, Vui lòng nhấn "/captcha THE_CAPTCHA" trong kênh chát!' +valid_captcha: '&2Mã captcha đã được xác nhận!' +kick_forvip: '&eChỉ có thành viên VIP mới được tham gia khi máy chủ đầy!' +kick_fullserver: '&4Máy chủ quá tải, vui lòng thử lại sau!' +usage_email_add: '&cSử dụng: /email add ' +usage_email_change: '&cSử dụng: /email change ' +usage_email_recovery: '&cSử dụng: /email recovery ' +new_email_invalid: '&cEmail mới không hợp lệ, vui lòng thử lại!' +old_email_invalid: '&cEmail củ không hợp lệ, vui lòng thử lại!' +email_invalid: '&cĐại chỉ email không hợp lệ, vui lòng thử lại!' +email_added: '&2Đại chỉ email đã thêm vào tài khoảng của bạn thành công!' +email_confirm: '&cVui lòng xác nhận địa chỉ email của bạn!' +email_changed: '&2Địa chỉ email đã thay đổi!' +email_send: '&2Email phục hồi đã được gửi thành công! Vui lòng kiễm tra hộp thư đến trong email của bạn.' +email_exists: '&cEmail phục hồi đã được gửi, bạn có thể hủy bỏ và gửi thư mới bằng cách sử dụng lệnh sau:' +country_banned: '&4Quốc gia của bạn bị cấm tham gia máy chủ này!' +antibot_auto_enabled: '&4[AntiBotService] AntiBot đã được kích hoạt do số lượng lớn kết nối đến máy chủ!' +antibot_auto_disabled: '&2[AntiBotService] AntiBot đã được tắt sau %m phút!' +email_already_used: '&4Địa chỉ email đã được sử dụng' +two_factor_create: '&2Mã bí mật của bạn là %code. Bạn có thể quét nó tại đây %url' +not_owner_error: 'Bạn không phải là chủ sở hữu tài khoảng này, hãy chọn tên khác!' +invalid_name_case: 'Bạn nên vào máy chủ với tên đăng nhập là %valid, không phải là %invalid.' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index fbf89b5e..349787c5 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -15,6 +15,7 @@ login: '&8[&6玩家系统&8] &c已成功登录!' vb_nonActiv: '&8[&6玩家系统&8] &f你的帐号还未激活,请查看你的邮箱!' user_regged: '&8[&6玩家系统&8] &c此用户已经在此服务器注册过' usage_reg: '&8[&6玩家系统&8] &c正确用法:“/register <密码> <再输入一次以确定密码>”' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&8[&6玩家系统&8] &f你不允许再为你的IP在服务器注册更多用户了!' no_perm: '&8[&6玩家系统&8] &c没有权限' error: '&8[&6玩家系统&8] &f发现错误,请联系管理员' @@ -63,4 +64,8 @@ email_already_used: '&8[&6玩家系统&8] &4邮箱已被使用' email_exists: '&8[&6玩家系统&8] &c恢复邮件已发送 ! 你可以丢弃它然後使用以下的指令来发送新的邮件:' two_factor_create: '&8[&6玩家系统&8] &2你的代码是 %code,你可以使用 %url 来扫描' not_owner_error: '&8[&6玩家系统&8] &4警告! &c你并不是此帐户持有人,请立即登出。 ' -invalid_name_case: '&8[&6玩家系统&8] &c你应该使用「%valid」而并非「%invalid」登入游戏。 ' \ No newline at end of file +invalid_name_case: '&8[&6玩家系统&8] &c你应该使用「%valid」而并非「%invalid」登入游戏。 ' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index 3dc579c7..7001604c 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -15,6 +15,7 @@ login: '&8[&6用戶系統&8] &c你成功登入了。' vb_nonActiv: '&8[&6用戶系統&8] &f你的帳戶還沒有經過電郵驗證 !' user_regged: '&8[&6用戶系統&8] &c此用戶名已經註冊過了。' usage_reg: '&8[&6用戶系統&8] &f用法: 《 /register <密碼> <重覆密碼> 》' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&8[&6用戶系統&8] &f你的IP地址已達到註冊數上限。' no_perm: '&8[&6用戶系統&8] &b嗯~你想幹甚麼?' error: '&8[&6用戶系統&8] &f發生錯誤,請與管理員聯絡。' @@ -63,4 +64,8 @@ email_already_used: '&8[&6用戶系統&8] &4這個電郵地址已被使用。' email_exists: '&8[&6用戶系統&8] &c訊息已發送!如果你收不到該封電郵,可以使用以下指令進行重寄:' two_factor_create: '&8[&6用戶系統 - 兩步驗證碼&8] &b你的登入金鑰為&9「%c%code&9」&b,掃描連結為:&c %url' not_owner_error: '&8[&6用戶系統&8] &4警告!&c你並不是此帳戶持有人,請立即登出。' -invalid_name_case: '&8[&6用戶系統&8] &4警告!&c你應該使用「%valid」而並非「%invalid」登入遊戲。' \ No newline at end of file +invalid_name_case: '&8[&6用戶系統&8] &4警告!&c你應該使用「%valid」而並非「%invalid」登入遊戲。' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index 9b0d536c..f50208b0 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -15,6 +15,7 @@ login: '&b【AuthMe】&6密碼正確,你已成功登入!' vb_nonActiv: '&b【AuthMe】&6你的帳號還沒有經過驗證! 檢查看看你的電子信箱 (Email) 吧!' user_regged: '&b【AuthMe】&6這個帳號已經被註冊過了!' usage_reg: '&b【AuthMe】&6用法: &c"/register <密碼> <確認密碼>"' +# TODO max_reg: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&b【AuthMe】&6你的 IP 位置所註冊的帳號數量已經達到最大。' no_perm: '&b【AuthMe】&6你沒有使用該指令的權限。' error: '&b【AuthMe】&6發生錯誤,請聯繫管理員' @@ -63,4 +64,8 @@ email_already_used: '&b【AuthMe】&4這個電郵地址已被使用。' email_exists: '&b【AuthMe】&6這個帳戶已經有設定電子郵件了' two_factor_create: '&b【AuthMe - 兩步驗證碼】&b你的登入金鑰為&9「%c%code&9」&b,掃描連結為:&c %url' not_owner_error: '&b【AuthMe】&4警告!&c你並不是此帳戶持有人,請立即登出。' -invalid_name_case: '&b【AuthMe】&4警告!&c你應該使用「%valid」而並非「%invalid」登入遊戲。' \ No newline at end of file +invalid_name_case: '&b【AuthMe】&4警告!&c你應該使用「%valid」而並非「%invalid」登入遊戲。' +# TODO denied_command: '&cIn order to use this command you must be authenticated!' +# TODO same_ip_online: 'A player with the same IP is already in game!' +# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +# TODO denied_chat: '&cIn order to chat you must be authenticated!' \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index bf63b364..ef48d2e2 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ -name: ${pluginName} -authors: [${pluginAuthors}] +name: ${bukkitplugin.name} +authors: [${bukkitplugin.authors}] website: ${project.url} description: ${project.description} -main: ${mainClass} -version: ${project.version}-b${buildNumber} +main: ${bukkitplugin.main} +version: ${project.versionCode} softdepend: - Vault - PermissionsBukkit @@ -28,12 +28,14 @@ commands: changepassword: description: Change password of an account usage: /changepassword + aliases: [cp,changepass] logout: description: Logout usage: /logout unregister: description: Unregister your account usage: /unregister + aliases: [unreg] email: description: Add Email or recover password usage: '/email add your@email.com your@email.com|change oldEmail newEmail|recovery your@email.com' diff --git a/src/test/java/fr/xephi/authme/AntiBotTest.java b/src/test/java/fr/xephi/authme/AntiBotTest.java index 9950963a..ddf41831 100644 --- a/src/test/java/fr/xephi/authme/AntiBotTest.java +++ b/src/test/java/fr/xephi/authme/AntiBotTest.java @@ -164,7 +164,7 @@ public class AntiBotTest { AntiBot antiBot = createListeningAntiBot(); // when - antiBot.checkAntiBot(player); + antiBot.handlePlayerJoin(player); // then @SuppressWarnings("unchecked") @@ -194,7 +194,7 @@ public class AntiBotTest { AntiBot antiBot = createListeningAntiBot(); // when - antiBot.checkAntiBot(player); + antiBot.handlePlayerJoin(player); // then @SuppressWarnings("rawtypes") diff --git a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java index 33579e80..397181bb 100644 --- a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java +++ b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java @@ -27,7 +27,7 @@ public final class ReflectionTestUtils { Field field = getField(clazz, instance, fieldName); field.set(instance, value); } catch (IllegalAccessException e) { - throw new RuntimeException( + throw new UnsupportedOperationException( format("Could not set value to field '%s' for instance '%s' of class '%s'", fieldName, instance, clazz.getName()), e); } @@ -39,7 +39,7 @@ public final class ReflectionTestUtils { field.setAccessible(true); return field; } catch (NoSuchFieldException e) { - throw new RuntimeException(format("Could not get field '%s' for instance '%s' of class '%s'", + throw new UnsupportedOperationException(format("Could not get field '%s' for instance '%s' of class '%s'", fieldName, instance, clazz.getName()), e); } } @@ -50,7 +50,7 @@ public final class ReflectionTestUtils { try { return field.get(instance); } catch (IllegalAccessException e) { - throw new RuntimeException("Could not get value of field '" + fieldName + "'"); + throw new UnsupportedOperationException("Could not get value of field '" + fieldName + "'"); } } @@ -69,7 +69,7 @@ public final class ReflectionTestUtils { method.setAccessible(true); return method; } catch (NoSuchMethodException e) { - throw new RuntimeException("Could not retrieve method '" + methodName + "' from class '" + throw new UnsupportedOperationException("Could not retrieve method '" + methodName + "' from class '" + clazz.getName() + "'"); } } diff --git a/src/test/java/fr/xephi/authme/TestHelper.java b/src/test/java/fr/xephi/authme/TestHelper.java index 463b3870..bef65621 100644 --- a/src/test/java/fr/xephi/authme/TestHelper.java +++ b/src/test/java/fr/xephi/authme/TestHelper.java @@ -1,7 +1,7 @@ package fr.xephi.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.util.BukkitService; +import org.bukkit.entity.Player; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -9,12 +9,18 @@ import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.logging.Logger; +import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** @@ -32,8 +38,8 @@ public final class TestHelper { * @return The project file */ public static File getJarFile(String path) { - URL url = getUrlOrThrow(path); - return new File(url.getFile()); + URI uri = getUriOrThrow(path); + return new File(uri.getPath()); } /** @@ -43,7 +49,7 @@ public final class TestHelper { * @return The Path object to the file */ public static Path getJarPath(String path) { - String sqlFilePath = getUrlOrThrow(path).getPath(); + String sqlFilePath = getUriOrThrow(path).getPath(); // Windows preprends the path with a '/' or '\', which Paths cannot handle String appropriatePath = System.getProperty("os.name").contains("indow") ? sqlFilePath.substring(1) @@ -51,28 +57,46 @@ public final class TestHelper { return Paths.get(appropriatePath); } - private static URL getUrlOrThrow(String path) { + private static URI getUriOrThrow(String path) { URL url = TestHelper.class.getResource(path); if (url == null) { throw new IllegalStateException("File '" + path + "' could not be loaded"); } - return url; + try { + return new URI(url.toString()); + } catch (URISyntaxException e) { + throw new IllegalStateException("File '" + path + "' cannot be converted to a URI"); + } } /** - * Execute a {@link Runnable} passed to a mock's {@link CommandService#runTaskAsynchronously} method. + * Execute a {@link Runnable} passed to a mock's {@link BukkitService#runTaskAsynchronously} method. * Note that calling this method expects that there be a runnable sent to the method and will fail * otherwise. * * @param service The mock service */ - public static void runInnerRunnable(CommandService service) { + public static void runInnerRunnable(BukkitService service) { ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); verify(service).runTaskAsynchronously(captor.capture()); Runnable runnable = captor.getValue(); runnable.run(); } + /** + * Execute a {@link Runnable} passed to a mock's {@link BukkitService#scheduleSyncDelayedTask(Runnable)} + * method. Note that calling this method expects that there be a runnable sent to the method and will fail + * otherwise. + * + * @param service The mock service + */ + public static void runSyncDelayedTask(BukkitService service) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + verify(service).scheduleSyncDelayedTask(captor.capture()); + Runnable runnable = captor.getValue(); + runnable.run(); + } + /** * Execute a {@link Runnable} passed to a mock's {@link BukkitService#scheduleSyncDelayedTask(Runnable, long)} * method. Note that calling this method expects that there be a runnable sent to the method and will fail @@ -125,4 +149,17 @@ public final class TestHelper { } } + /** + * Configures the player mock to return the given IP address. + * + * @param player the player mock + * @param ip the ip address it should return + */ + public static void mockPlayerIp(Player player, String ip) { + InetAddress inetAddress = mock(InetAddress.class); + given(inetAddress.getHostAddress()).willReturn(ip); + InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, 8093); + given(player.getAddress()).willReturn(inetSocketAddress); + } + } diff --git a/src/test/java/fr/xephi/authme/cache/CaptchaManagerTest.java b/src/test/java/fr/xephi/authme/cache/CaptchaManagerTest.java index 96489155..83fe4b34 100644 --- a/src/test/java/fr/xephi/authme/cache/CaptchaManagerTest.java +++ b/src/test/java/fr/xephi/authme/cache/CaptchaManagerTest.java @@ -1,10 +1,14 @@ package fr.xephi.authme.cache; +import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.SecuritySettings; import org.junit.Test; +import java.util.Map; + import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -39,7 +43,7 @@ public class CaptchaManagerTest { String player = "Miner"; NewSetting settings = mockSettings(1, 4); CaptchaManager manager = new CaptchaManager(settings); - String captchaCode = manager.getCaptchaCode(player); + String captchaCode = manager.getCaptchaCodeOrGenerateNew(player); // when boolean badResult = manager.checkCode(player, "wrong_code"); @@ -53,11 +57,105 @@ public class CaptchaManagerTest { assertThat(manager.checkCode(player, "bogus"), equalTo(true)); } + /** + * Tests {@link CaptchaManager#getCaptchaCode} and {@link CaptchaManager#getCaptchaCodeOrGenerateNew}. + * The former method should never change the code (and so return {@code null} for no code) while the latter should + * generate a new code if no code is yet present. If a code is saved, it should never generate a new one. + */ + @Test + public void shouldHaveSameCodeAfterGeneration() { + // given + String player = "Tester"; + NewSetting settings = mockSettings(1, 5); + CaptchaManager manager = new CaptchaManager(settings); + + // when + String code1 = manager.getCaptchaCode(player); + String code2 = manager.getCaptchaCodeOrGenerateNew(player); + String code3 = manager.getCaptchaCode(player); + String code4 = manager.getCaptchaCodeOrGenerateNew(player); + String code5 = manager.getCaptchaCode(player); + + // then + assertThat(code1, nullValue()); + assertThat(code2.length(), equalTo(5)); + assertThat(code3, equalTo(code2)); + assertThat(code4, equalTo(code2)); + assertThat(code5, equalTo(code2)); + } + + @Test + public void shouldIncreaseAndResetCount() { + // given + String player = "plaYer"; + NewSetting settings = mockSettings(2, 3); + CaptchaManager manager = new CaptchaManager(settings); + + // when + manager.increaseCount(player); + manager.increaseCount(player); + + // then + assertThat(manager.isCaptchaRequired(player), equalTo(true)); + assertHasCount(manager, player, 2); + + // when 2 + manager.resetCounts(player); + + // then 2 + assertThat(manager.isCaptchaRequired(player), equalTo(false)); + assertHasCount(manager, player, null); + } + + @Test + public void shouldNotIncreaseCountForDisabledCaptcha() { + // given + String player = "someone_"; + NewSetting settings = mockSettings(1, 3); + given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(false); + CaptchaManager manager = new CaptchaManager(settings); + + // when + manager.increaseCount(player); + + // then + assertThat(manager.isCaptchaRequired(player), equalTo(false)); + assertHasCount(manager, player, null); + } + + @Test + public void shouldNotCheckCountIfCaptchaIsDisabled() { + // given + String player = "Robert001"; + NewSetting settings = mockSettings(1, 5); + CaptchaManager manager = new CaptchaManager(settings); + given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(false); + + // when + manager.increaseCount(player); + // assumptions + assertThat(manager.isCaptchaRequired(player), equalTo(true)); + assertHasCount(manager, player, 1); + // end assumptions + manager.loadSettings(settings); + boolean result = manager.isCaptchaRequired(player); + + // then + assertThat(result, equalTo(false)); + } private static NewSetting mockSettings(int maxTries, int captchaLength) { NewSetting settings = mock(NewSetting.class); + given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(true); given(settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA)).willReturn(maxTries); given(settings.getProperty(SecuritySettings.CAPTCHA_LENGTH)).willReturn(captchaLength); return settings; } + + private static void assertHasCount(CaptchaManager manager, String player, Integer count) { + @SuppressWarnings("unchecked") + Map playerCounts = (Map) ReflectionTestUtils + .getFieldValue(CaptchaManager.class, manager, "playerCounts"); + assertThat(playerCounts.get(player.toLowerCase()), equalTo(count)); + } } diff --git a/src/test/java/fr/xephi/authme/cache/TempbanManagerTest.java b/src/test/java/fr/xephi/authme/cache/TempbanManagerTest.java new file mode 100644 index 00000000..b3c6fc03 --- /dev/null +++ b/src/test/java/fr/xephi/authme/cache/TempbanManagerTest.java @@ -0,0 +1,203 @@ +package fr.xephi.authme.cache; + +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Calendar; +import java.util.Date; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link TempbanManager}. + */ +@RunWith(MockitoJUnitRunner.class) +public class TempbanManagerTest { + + private static final long DATE_TOLERANCE_MILLISECONDS = 100L; + + @Mock + private BukkitService bukkitService; + + @Mock + private Messages messages; + + @Test + public void shouldAddCounts() { + // given + NewSetting settings = mockSettings(3, 60); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + String address = "192.168.1.1"; + + // when + for (int i = 0; i < 2; ++i) { + manager.increaseCount(address); + } + + // then + assertThat(manager.shouldTempban(address), equalTo(false)); + manager.increaseCount(address); + assertThat(manager.shouldTempban(address), equalTo(true)); + assertThat(manager.shouldTempban("10.0.0.1"), equalTo(false)); + } + + @Test + public void shouldIncreaseAndResetCount() { + // given + String address = "192.168.1.2"; + NewSetting settings = mockSettings(3, 60); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + + // when + manager.increaseCount(address); + manager.increaseCount(address); + manager.increaseCount(address); + + // then + assertThat(manager.shouldTempban(address), equalTo(true)); + assertHasCount(manager, address, 3); + + // when 2 + manager.resetCount(address); + + // then 2 + assertThat(manager.shouldTempban(address), equalTo(false)); + assertHasCount(manager, address, null); + } + + @Test + public void shouldNotIncreaseCountForDisabledTempban() { + // given + String address = "192.168.1.3"; + NewSetting settings = mockSettings(1, 5); + given(settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS)).willReturn(false); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + + // when + manager.increaseCount(address); + + // then + assertThat(manager.shouldTempban(address), equalTo(false)); + assertHasCount(manager, address, null); + } + + @Test + public void shouldNotCheckCountIfTempbanIsDisabled() { + // given + String address = "192.168.1.4"; + NewSetting settings = mockSettings(1, 5); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + given(settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS)).willReturn(false); + + // when + manager.increaseCount(address); + // assumptions + assertThat(manager.shouldTempban(address), equalTo(true)); + assertHasCount(manager, address, 1); + // end assumptions + manager.loadSettings(settings); + boolean result = manager.shouldTempban(address); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldNotIssueBanIfDisabled() { + // given + NewSetting settings = mockSettings(0, 0); + given(settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS)).willReturn(false); + Player player = mock(Player.class); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + + // when + manager.tempbanPlayer(player); + + // then + verifyZeroInteractions(player, bukkitService); + } + + @Test + public void shouldBanPlayerIp() { + // given + Player player = mock(Player.class); + String ip = "123.45.67.89"; + TestHelper.mockPlayerIp(player, ip); + String banReason = "IP ban too many logins"; + given(messages.retrieveSingle(MessageKey.TEMPBAN_MAX_LOGINS)).willReturn(banReason); + NewSetting settings = mockSettings(2, 100); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + + // when + manager.tempbanPlayer(player); + TestHelper.runSyncDelayedTask(bukkitService); + + // then + verify(player).kickPlayer(banReason); + ArgumentCaptor captor = ArgumentCaptor.forClass(Date.class); + verify(bukkitService).banIp(eq(ip), eq(banReason), captor.capture(), eq("AuthMe")); + + // Compute the expected expiration date and check that the actual date is within the difference tolerance + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MINUTE, 100); + long expectedExpiration = cal.getTime().getTime(); + assertThat(Math.abs(captor.getValue().getTime() - expectedExpiration), lessThan(DATE_TOLERANCE_MILLISECONDS)); + } + + @Test + public void shouldResetCountAfterBan() { + // given + Player player = mock(Player.class); + String ip = "22.44.66.88"; + TestHelper.mockPlayerIp(player, ip); + String banReason = "kick msg"; + given(messages.retrieveSingle(MessageKey.TEMPBAN_MAX_LOGINS)).willReturn(banReason); + NewSetting settings = mockSettings(10, 60); + TempbanManager manager = new TempbanManager(bukkitService, messages, settings); + manager.increaseCount(ip); + manager.increaseCount(ip); + manager.increaseCount(ip); + + // when + manager.tempbanPlayer(player); + TestHelper.runSyncDelayedTask(bukkitService); + + // then + verify(player).kickPlayer(banReason); + assertHasCount(manager, ip, null); + } + + private static NewSetting mockSettings(int maxTries, int tempbanLength) { + NewSetting settings = mock(NewSetting.class); + given(settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS)).willReturn(true); + given(settings.getProperty(SecuritySettings.MAX_LOGIN_TEMPBAN)).willReturn(maxTries); + given(settings.getProperty(SecuritySettings.TEMPBAN_LENGTH)).willReturn(tempbanLength); + return settings; + } + + private static void assertHasCount(TempbanManager manager, String address, Integer count) { + @SuppressWarnings("unchecked") + Map playerCounts = (Map) ReflectionTestUtils + .getFieldValue(TempbanManager.class, manager, "ipLoginFailureCounts"); + assertThat(playerCounts.get(address), equalTo(count)); + } +} diff --git a/src/test/java/fr/xephi/authme/command/CommandConsistencyTest.java b/src/test/java/fr/xephi/authme/command/CommandConsistencyTest.java new file mode 100644 index 00000000..d2033d1b --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/CommandConsistencyTest.java @@ -0,0 +1,84 @@ +package fr.xephi.authme.command; + +import org.bukkit.configuration.MemorySection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static fr.xephi.authme.TestHelper.getJarFile; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Checks that the commands declared in plugin.yml correspond + * to the ones built by the {@link CommandInitializer}. + */ +public class CommandConsistencyTest { + + @Test + public void shouldHaveEqualDefinitions() { + // given + Collection> initializedCommands = initializeCommands(); + Map> pluginFileLabels = getLabelsFromPluginFile(); + + // when / then + assertThat("number of base commands are equal in plugin.yml and CommandInitializer", + initializedCommands.size(), equalTo(pluginFileLabels.size())); + for (List commandLabels : initializedCommands) { + List pluginYmlLabels = pluginFileLabels.get(commandLabels.get(0)); + // NB: the first label in CommandDescription needs to correspond to the key in plugin.yml + assertThat("plugin.yml contains definition for command '" + commandLabels.get(0) + "'", + pluginYmlLabels, not(nullValue())); + assertThat("plugin.yml and CommandDescription have same alternative labels for /" + commandLabels.get(0), + pluginYmlLabels, containsInAnyOrder(commandLabels.subList(1, commandLabels.size()).toArray())); + } + } + + /** + * Gets the command definitions from CommandInitializer and returns the + * labels of all base commands. + * + * @return collection of all base command labels + */ + private static Collection> initializeCommands() { + CommandInitializer initializer = new CommandInitializer(); + Collection> commandLabels = new ArrayList<>(); + for (CommandDescription baseCommand : initializer.getCommands()) { + commandLabels.add(baseCommand.getLabels()); + } + return commandLabels; + } + + /** + * Reads plugin.yml and returns the defined commands by main label and aliases. + * + * @return collection of all labels and their aliases + */ + @SuppressWarnings("unchecked") + private static Map> getLabelsFromPluginFile() { + FileConfiguration pluginFile = YamlConfiguration.loadConfiguration(getJarFile("/plugin.yml")); + MemorySection commandList = (MemorySection) pluginFile.get("commands"); + Map commandDefinitions = commandList.getValues(false); + + Map> commandLabels = new HashMap<>(); + for (Map.Entry commandDefinition : commandDefinitions.entrySet()) { + MemorySection definition = (MemorySection) commandDefinition.getValue(); + List alternativeLabels = definition.get("aliases") == null + ? Collections.EMPTY_LIST + : (List) definition.get("aliases"); + commandLabels.put(commandDefinition.getKey(), alternativeLabels); + } + return commandLabels; + } + +} diff --git a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java b/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java index e5e6b749..137d9f1f 100644 --- a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java @@ -1,15 +1,25 @@ package fr.xephi.authme.command; +import com.google.common.collect.Sets; +import fr.xephi.authme.command.TestCommandsUtil.TestLoginCommand; +import fr.xephi.authme.command.TestCommandsUtil.TestRegisterCommand; +import fr.xephi.authme.command.TestCommandsUtil.TestUnregisterCommand; +import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.command.CommandSender; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.MockitoAnnotations; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import java.util.Collections; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS; import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; @@ -17,7 +27,6 @@ import static fr.xephi.authme.command.FoundResultStatus.NO_PERMISSION; import static fr.xephi.authme.command.FoundResultStatus.SUCCESS; import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; import static java.util.Arrays.asList; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; @@ -27,6 +36,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -35,24 +45,59 @@ import static org.mockito.Mockito.verify; /** * Test for {@link CommandHandler}. */ -@SuppressWarnings("ArraysAsListWithZeroOrOneArgument") // Justification: It's more readable to use asList() everywhere in the test when we often generated two lists where one // often consists of only one element, e.g. myMethod(asList("authme"), asList("my", "args"), ...) +@SuppressWarnings("ArraysAsListWithZeroOrOneArgument") +@RunWith(MockitoJUnitRunner.class) public class CommandHandlerTest { private CommandHandler handler; - private CommandService serviceMock; - @Captor - private ArgumentCaptor> captor; + @Mock + private AuthMeServiceInitializer initializer; + @Mock + private CommandMapper commandMapper; + @Mock + private PermissionsManager permissionsManager; + @Mock + private HelpProvider helpProvider; + + private Map, ExecutableCommand> mockedCommands = new HashMap<>(); @Before - public void setUpCommandHandler() { - MockitoAnnotations.initMocks(this); - serviceMock = mock(CommandService.class); - handler = new CommandHandler(serviceMock); + @SuppressWarnings("unchecked") + public void initializeCommandMapper() { + given(commandMapper.getCommandClasses()).willReturn(Sets.newHashSet(ExecutableCommand.class, + TestLoginCommand.class, TestRegisterCommand.class, TestUnregisterCommand.class)); + setInjectorToMockExecutableCommandClasses(); + handler = new CommandHandler(initializer, commandMapper, permissionsManager, helpProvider); } + /** + * Makes the initializer return a mock when {@link AuthMeServiceInitializer#newInstance(Class)} is invoked + * with (a child of) ExecutableCommand.class. The mocks the initializer creates are stored in {@link #mockedCommands}. + *

+ * The {@link CommandMapper} is mocked in {@link #initializeCommandMapper()} to return certain test classes. + */ + @SuppressWarnings("unchecked") + private void setInjectorToMockExecutableCommandClasses() { + given(initializer.newInstance(any(Class.class))).willAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Class clazz = (Class) invocation.getArguments()[0]; + if (ExecutableCommand.class.isAssignableFrom(clazz)) { + Class commandClass = (Class) clazz; + ExecutableCommand mock = mock(commandClass); + mockedCommands.put(commandClass, mock); + return mock; + } + throw new IllegalStateException("Unexpected class '" + clazz.getName() + + "': Not a child of ExecutableCommand"); + } + }); + } + + @Test public void shouldCallMappedCommandWithArgs() { // given @@ -60,22 +105,18 @@ public class CommandHandlerTest { String[] bukkitArgs = {"Login", "myPass"}; CommandSender sender = mock(CommandSender.class); - ExecutableCommand executableCommand = mock(ExecutableCommand.class); CommandDescription command = mock(CommandDescription.class); - given(command.getExecutableCommand()).willReturn(executableCommand); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))) + doReturn(TestLoginCommand.class).when(command).getExecutableCommand(); + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))) .willReturn(new FoundCommandResult(command, asList("Authme", "Login"), asList("myPass"), 0.0, SUCCESS)); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("Authme", "Login", "myPass")); - - verify(executableCommand).executeCommand(eq(sender), captor.capture(), any(CommandService.class)); - assertThat(captor.getValue(), contains("myPass")); - + ExecutableCommand executableCommand = mockedCommands.get(TestLoginCommand.class); + verify(commandMapper).mapPartsToCommand(sender, asList("Authme", "Login", "myPass")); + verify(executableCommand).executeCommand(sender, asList("myPass")); // Ensure that no error message was issued to the command sender verify(sender, never()).sendMessage(anyString()); } @@ -87,15 +128,14 @@ public class CommandHandlerTest { String[] bukkitArgs = {"testPlayer"}; CommandSender sender = mock(CommandSender.class); CommandDescription command = mock(CommandDescription.class); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))) + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))) .willReturn(new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, NO_PERMISSION)); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("unreg", "testPlayer")); + verify(commandMapper).mapPartsToCommand(sender, asList("unreg", "testPlayer")); verify(command, never()).getExecutableCommand(); verify(sender).sendMessage(argThat(containsString("don't have permission"))); } @@ -107,19 +147,15 @@ public class CommandHandlerTest { String[] bukkitArgs = {"testPlayer"}; CommandSender sender = mock(CommandSender.class); CommandDescription command = mock(CommandDescription.class); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, INCORRECT_ARGUMENTS)); - PermissionsManager permissionsManager = mock(PermissionsManager.class); - given(permissionsManager.hasPermission(sender, command)).willReturn(true); - given(serviceMock.getPermissionsManager()).willReturn(permissionsManager); + given(permissionsManager.hasPermission(sender, command.getPermission())).willReturn(true); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("unreg", "testPlayer")); - + verify(commandMapper).mapPartsToCommand(sender, asList("unreg", "testPlayer")); verify(command, never()).getExecutableCommand(); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(sender, atLeastOnce()).sendMessage(captor.capture()); @@ -133,19 +169,15 @@ public class CommandHandlerTest { String[] bukkitArgs = {"testPlayer"}; CommandSender sender = mock(CommandSender.class); CommandDescription command = mock(CommandDescription.class); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, INCORRECT_ARGUMENTS)); - PermissionsManager permissionsManager = mock(PermissionsManager.class); - given(permissionsManager.hasPermission(sender, command)).willReturn(false); - given(serviceMock.getPermissionsManager()).willReturn(permissionsManager); + given(permissionsManager.hasPermission(sender, command.getPermission())).willReturn(false); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("unreg", "testPlayer")); - + verify(commandMapper).mapPartsToCommand(sender, asList("unreg", "testPlayer")); verify(command, never()).getExecutableCommand(); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(sender).sendMessage(captor.capture()); @@ -159,15 +191,14 @@ public class CommandHandlerTest { String[] bukkitArgs = {"testPlayer"}; CommandSender sender = mock(CommandSender.class); CommandDescription command = mock(CommandDescription.class); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, MISSING_BASE_COMMAND)); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("unreg", "testPlayer")); + verify(commandMapper).mapPartsToCommand(sender, asList("unreg", "testPlayer")); verify(command, never()).getExecutableCommand(); verify(sender).sendMessage(argThat(containsString("Failed to parse"))); } @@ -180,16 +211,14 @@ public class CommandHandlerTest { CommandSender sender = mock(CommandSender.class); CommandDescription command = mock(CommandDescription.class); given(command.getLabels()).willReturn(Collections.singletonList("test_cmd")); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.01, UNKNOWN_LABEL)); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("unreg", "testPlayer")); - + verify(commandMapper).mapPartsToCommand(sender, asList("unreg", "testPlayer")); verify(command, never()).getExecutableCommand(); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(sender, times(3)).sendMessage(captor.capture()); @@ -208,16 +237,14 @@ public class CommandHandlerTest { CommandSender sender = mock(CommandSender.class); CommandDescription command = mock(CommandDescription.class); given(command.getLabels()).willReturn(Collections.singletonList("test_cmd")); - given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( + given(commandMapper.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn( new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 1.0, UNKNOWN_LABEL)); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("unreg", "testPlayer")); - + verify(commandMapper).mapPartsToCommand(sender, asList("unreg", "testPlayer")); verify(command, never()).getExecutableCommand(); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(sender, times(2)).sendMessage(captor.capture()); @@ -230,25 +257,21 @@ public class CommandHandlerTest { public void shouldStripWhitespace() { // given String bukkitLabel = "AuthMe"; - String[] bukkitArgs = {" ", "", "LOGIN", " ", "testArg", " "}; + String[] bukkitArgs = {" ", "", "REGISTER", " ", "testArg", " "}; CommandSender sender = mock(CommandSender.class); - ExecutableCommand executableCommand = mock(ExecutableCommand.class); CommandDescription command = mock(CommandDescription.class); - given(command.getExecutableCommand()).willReturn(executableCommand); - given(serviceMock.mapPartsToCommand(eq(sender), anyListOf(String.class))) - .willReturn(new FoundCommandResult(command, asList("AuthMe", "LOGIN"), asList("testArg"), 0.0, SUCCESS)); + doReturn(TestRegisterCommand.class).when(command).getExecutableCommand(); + given(commandMapper.mapPartsToCommand(eq(sender), anyListOf(String.class))) + .willReturn(new FoundCommandResult(command, asList("AuthMe", "REGISTER"), asList("testArg"), 0.0, SUCCESS)); // when handler.processCommand(sender, bukkitLabel, bukkitArgs); // then - verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture()); - assertThat(captor.getValue(), contains("AuthMe", "LOGIN", "testArg")); - - verify(command.getExecutableCommand()).executeCommand(eq(sender), captor.capture(), eq(serviceMock)); - assertThat(captor.getValue(), contains("testArg")); - + ExecutableCommand executableCommand = mockedCommands.get(TestRegisterCommand.class); + verify(commandMapper).mapPartsToCommand(sender, asList("AuthMe", "REGISTER", "testArg")); + verify(executableCommand).executeCommand(sender, asList("testArg")); verify(sender, never()).sendMessage(anyString()); } diff --git a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java index 6e5418d5..669f71d8 100644 --- a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java @@ -17,8 +17,6 @@ import java.util.regex.Pattern; import static fr.xephi.authme.permission.DefaultPermission.OP_ONLY; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -36,8 +34,9 @@ public class CommandInitializerTest { private static Set commands; @BeforeClass - public static void initializeCommandManager() { - commands = CommandInitializer.buildCommands(); + public static void initializeCommandCollection() { + CommandInitializer commandInitializer = new CommandInitializer(); + commands = commandInitializer.getCommands(); } @Test @@ -157,34 +156,6 @@ public class CommandInitializerTest { walkThroughCommands(commands, descriptionTester); } - /** - * Check that the implementation of {@link ExecutableCommand} a command points to is the same for each type: - * it is inefficient to instantiate the same type multiple times. - */ - @Test - public void shouldNotHaveMultipleInstancesOfSameExecutableCommandSubType() { - // given - final Map, ExecutableCommand> implementations = new HashMap<>(); - BiConsumer descriptionTester = new BiConsumer() { - @Override - public void accept(CommandDescription command, int depth) { - assertThat(command.getExecutableCommand(), not(nullValue())); - ExecutableCommand commandExec = command.getExecutableCommand(); - ExecutableCommand storedExec = implementations.get(command.getExecutableCommand().getClass()); - if (storedExec != null) { - assertThat("has same implementation of '" + storedExec.getClass().getName() + "' for command with " - + "parent " + (command.getParent() == null ? "null" : command.getParent().getLabels()), - storedExec == commandExec, equalTo(true)); - } else { - implementations.put(commandExec.getClass(), commandExec); - } - } - }; - - // when/then - walkThroughCommands(commands, descriptionTester); - } - @Test public void shouldHaveOptionalArgumentsAfterMandatoryOnes() { // given @@ -195,7 +166,7 @@ public class CommandInitializerTest { for (CommandArgumentDescription argument : command.getArguments()) { if (argument.isOptional()) { encounteredOptionalArg = true; - } else if (!argument.isOptional() && encounteredOptionalArg) { + } else if (encounteredOptionalArg) { fail("Mandatory arguments should come before optional ones for command with labels '" + command.getLabels() + "'"); } @@ -239,22 +210,16 @@ public class CommandInitializerTest { BiConsumer adminPermissionChecker = new BiConsumer() { @Override public void accept(CommandDescription command, int depth) { - CommandPermissions permissions = command.getCommandPermissions(); - if (permissions != null && OP_ONLY.equals(permissions.getDefaultPermission())) { - if (!hasAdminNode(permissions)) { - fail("The command with labels " + command.getLabels() + " has OP_ONLY default " - + "permission but no permission node on admin level"); - } + PermissionNode permission = command.getPermission(); + if (permission != null && OP_ONLY.equals(permission.getDefaultPermission()) + && !hasAdminNode(permission)) { + fail("The command with labels " + command.getLabels() + " has OP_ONLY default " + + "permission but no permission node on admin level"); } } - private boolean hasAdminNode(CommandPermissions permissions) { - for (PermissionNode node : permissions.getPermissionNodes()) { - if (node instanceof AdminPermission) { - return true; - } - } - return false; + private boolean hasAdminNode(PermissionNode permission) { + return permission instanceof AdminPermission; } }; @@ -280,15 +245,15 @@ public class CommandInitializerTest { } private void testCollectionForCommand(CommandDescription command, int argCount, Map, Integer> collection) { - final Class clazz = command.getExecutableCommand().getClass(); + final Class clazz = command.getExecutableCommand(); Integer existingCount = collection.get(clazz); - if (existingCount != null) { + if (existingCount == null) { + collection.put(clazz, argCount); + } else { String commandDescription = "Command with label '" + command.getLabels().get(0) + "' and parent '" - + (command.getParent() != null ? command.getLabels().get(0) : "null") + "' "; + + (command.getParent() == null ? "null" : command.getLabels().get(0)) + "' "; assertThat(commandDescription + "should point to " + clazz + " with arguments consistent to others", argCount, equalTo(existingCount)); - } else { - collection.put(clazz, argCount); } } }; diff --git a/src/test/java/fr/xephi/authme/command/CommandMapperTest.java b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java index 77840e89..28ee861f 100644 --- a/src/test/java/fr/xephi/authme/command/CommandMapperTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java @@ -1,12 +1,16 @@ package fr.xephi.authme.command; +import fr.xephi.authme.command.TestCommandsUtil.TestLoginCommand; +import fr.xephi.authme.command.TestCommandsUtil.TestRegisterCommand; +import fr.xephi.authme.command.TestCommandsUtil.TestUnregisterCommand; +import fr.xephi.authme.command.executable.HelpCommand; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.command.CommandSender; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import java.util.Arrays; import java.util.List; import java.util.Set; @@ -14,6 +18,7 @@ import static fr.xephi.authme.command.TestCommandsUtil.getCommandWithLabel; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; @@ -30,8 +35,8 @@ import static org.mockito.Mockito.mock; public class CommandMapperTest { private static Set commands; - private static CommandMapper mapper; - private static PermissionsManager permissionsManager; + private CommandMapper mapper; + private PermissionsManager permissionsManager; @BeforeClass public static void setUpCommandHandler() { @@ -41,7 +46,9 @@ public class CommandMapperTest { @Before public void setUpMocks() { permissionsManager = mock(PermissionsManager.class); - mapper = new CommandMapper(commands, permissionsManager); + CommandInitializer initializer = mock(CommandInitializer.class); + given(initializer.getCommands()).willReturn(commands); + mapper = new CommandMapper(initializer, permissionsManager); } // ----------- @@ -50,9 +57,9 @@ public class CommandMapperTest { @Test public void shouldMapPartsToLoginChildCommand() { // given - List parts = Arrays.asList("authme", "login", "test1"); + List parts = asList("authme", "login", "test1"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -69,9 +76,9 @@ public class CommandMapperTest { @Test public void shouldMapPartsToCommandWithNoCaseSensitivity() { // given - List parts = Arrays.asList("Authme", "REG", "arg1", "arg2"); + List parts = asList("Authme", "REG", "arg1", "arg2"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -87,9 +94,9 @@ public class CommandMapperTest { @Test public void shouldRejectCommandWithTooManyArguments() { // given - List parts = Arrays.asList("authme", "register", "pass123", "pass123", "pass123"); + List parts = asList("authme", "register", "pass123", "pass123", "pass123"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -105,9 +112,9 @@ public class CommandMapperTest { @Test public void shouldRejectCommandWithTooFewArguments() { // given - List parts = Arrays.asList("authme", "Reg"); + List parts = asList("authme", "Reg"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -123,9 +130,9 @@ public class CommandMapperTest { @Test public void shouldSuggestCommandWithSimilarLabel() { // given - List parts = Arrays.asList("authme", "reh", "pass123", "pass123"); + List parts = asList("authme", "reh", "pass123", "pass123"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -142,9 +149,9 @@ public class CommandMapperTest { @Test public void shouldSuggestMostSimilarCommand() { // given - List parts = Arrays.asList("authme", "asdfawetawty4asdca"); + List parts = asList("authme", "asdfawetawty4asdca"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -162,7 +169,7 @@ public class CommandMapperTest { // given List parts = singletonList("unregister"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -180,7 +187,7 @@ public class CommandMapperTest { // given List parts = asList("bogus", "label1", "arg1"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -205,7 +212,7 @@ public class CommandMapperTest { // given List parts = asList("Unreg", "player1"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -223,7 +230,7 @@ public class CommandMapperTest { // given List parts = asList("unregistER", "player1", "wrongArg"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -241,7 +248,7 @@ public class CommandMapperTest { // given List parts = asList("email", "helptest", "arg1"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -257,9 +264,9 @@ public class CommandMapperTest { @Test public void shouldRecognizeMissingPermissionForCommand() { // given - List parts = Arrays.asList("authme", "login", "test1"); + List parts = asList("authme", "login", "test1"); CommandSender sender = mock(CommandSender.class); - given(permissionsManager.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(false); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(false); // when FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); @@ -273,4 +280,15 @@ public class CommandMapperTest { assertThat(result.getArguments(), contains(parts.get(2))); } + @SuppressWarnings("unchecked") + @Test + public void shouldReturnExecutableCommandClasses() { + // given / when + Set> commandClasses = mapper.getCommandClasses(); + + // then + assertThat(commandClasses, containsInAnyOrder(ExecutableCommand.class, HelpCommand.class, + TestLoginCommand.class, TestRegisterCommand.class, TestUnregisterCommand.class)); + } + } diff --git a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java index 89a35571..3a3b5312 100644 --- a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java @@ -1,38 +1,23 @@ package fr.xephi.authme.command; -import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.command.help.HelpProvider; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.process.Management; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.domain.Property; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.ValidationService; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import java.util.Arrays; -import java.util.List; - import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** @@ -41,37 +26,14 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class CommandServiceTest { + @InjectMocks private CommandService commandService; @Mock - private AuthMe authMe; - @Mock - private CommandMapper commandMapper; - @Mock - private HelpProvider helpProvider; - @Mock private Messages messages; @Mock - private PasswordSecurity passwordSecurity; - @Mock - private PermissionsManager permissionsManager; - @Mock private NewSetting settings; @Mock - private PluginHooks pluginHooks; - @Mock - private SpawnLoader spawnLoader; - @Mock - private AntiBot antiBot; - @Mock private ValidationService validationService; - @Mock - private BukkitService bukkitService; - - @Before - public void setUpService() { - commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity, - permissionsManager, settings, pluginHooks, spawnLoader, antiBot, validationService, bukkitService); - } @Test public void shouldSendMessage() { @@ -97,85 +59,6 @@ public class CommandServiceTest { verify(messages).send(sender, MessageKey.ANTIBOT_AUTO_ENABLED_MESSAGE, "10"); } - @Test - public void shouldMapPartsToCommand() { - // given - CommandSender sender = mock(Player.class); - List commandParts = Arrays.asList("authme", "test", "test2"); - FoundCommandResult givenResult = mock(FoundCommandResult.class); - given(commandMapper.mapPartsToCommand(sender, commandParts)).willReturn(givenResult); - - // when - FoundCommandResult result = commandService.mapPartsToCommand(sender, commandParts); - - // then - assertThat(result, equalTo(givenResult)); - verify(commandMapper).mapPartsToCommand(sender, commandParts); - } - - @Test - public void shouldGetDataSource() { - // given - DataSource dataSource = mock(DataSource.class); - given(authMe.getDataSource()).willReturn(dataSource); - - // when - DataSource result = commandService.getDataSource(); - - // then - assertThat(result, equalTo(dataSource)); - } - - @Test - public void shouldGetPasswordSecurity() { - // given/when - PasswordSecurity passwordSecurity = commandService.getPasswordSecurity(); - - // then - assertThat(passwordSecurity, equalTo(this.passwordSecurity)); - } - - @Test - public void shouldOutputHelp() { - // given - CommandSender sender = mock(CommandSender.class); - FoundCommandResult result = mock(FoundCommandResult.class); - int options = HelpProvider.SHOW_LONG_DESCRIPTION; - List messages = Arrays.asList("Test message 1", "Other test message", "Third message for test"); - given(helpProvider.printHelp(sender, result, options)).willReturn(messages); - - // when - commandService.outputHelp(sender, result, options); - - // then - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(sender, times(3)).sendMessage(captor.capture()); - assertThat(captor.getAllValues(), equalTo(messages)); - } - - @Test - public void shouldReturnManagementObject() { - // given - Management management = mock(Management.class); - given(authMe.getManagement()).willReturn(management); - - // when - Management result = commandService.getManagement(); - - // then - assertThat(result, equalTo(management)); - verify(authMe).getManagement(); - } - - @Test - public void shouldReturnPermissionsManager() { - // given / when - PermissionsManager result = commandService.getPermissionsManager(); - - // then - assertThat(result, equalTo(permissionsManager)); - } - @Test public void shouldRetrieveMessage() { // given @@ -214,30 +97,6 @@ public class CommandServiceTest { assertThat(result, equalTo(settings)); } - @Test - public void shouldReturnAuthMe() { - // given/when - AuthMe result = commandService.getAuthMe(); - - // then - assertThat(result, equalTo(authMe)); - } - - @Test - public void shouldValidatePassword() { - // given - String user = "asdf"; - String password = "mySecret55"; - given(validationService.validatePassword(password, user)).willReturn(MessageKey.INVALID_PASSWORD_LENGTH); - - // when - MessageKey result = commandService.validatePassword(password, user); - - // then - assertThat(result, equalTo(MessageKey.INVALID_PASSWORD_LENGTH)); - verify(validationService).validatePassword(password, user); - } - @Test public void shouldValidateEmail() { // given @@ -268,19 +127,4 @@ public class CommandServiceTest { verify(validationService).isEmailFreeForRegistration(email, sender); } - @Test - public void shouldGetPlayer() { - // given - String playerName = "_tester"; - Player player = mock(Player.class); - given(bukkitService.getPlayerExact(playerName)).willReturn(player); - - // when - Player result = commandService.getPlayer(playerName); - - // then - assertThat(result, equalTo(player)); - verify(bukkitService).getPlayerExact(playerName); - } - } diff --git a/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java b/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java index e3970fa6..bdff3c25 100644 --- a/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command; +import fr.xephi.authme.TestHelper; import org.junit.Test; import java.util.Arrays; @@ -8,7 +9,6 @@ import java.util.List; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; /** * Test for {@link CommandUtils}. @@ -58,14 +58,14 @@ public class CommandUtilsTest { .labels("authme", "auth") .description("Base") .detailedDescription("Test base command.") - .executableCommand(mock(ExecutableCommand.class)) + .executableCommand(ExecutableCommand.class) .build(); CommandDescription command = CommandDescription.builder() .parent(base) .labels("help", "h", "?") .description("Child") .detailedDescription("Test child command.") - .executableCommand(mock(ExecutableCommand.class)) + .executableCommand(ExecutableCommand.class) .build(); // when @@ -113,6 +113,12 @@ public class CommandUtilsTest { checkArgumentCount(command, 1, 3); } + @Test + public void shouldHaveHiddenConstructor() { + // given / when / then + TestHelper.validateHasOnlyPrivateEmptyConstructor(CommandUtils.class); + } + private static void checkArgumentCount(CommandDescription command, int expectedMin, int expectedMax) { assertThat(CommandUtils.getMinNumberOfArguments(command), equalTo(expectedMin)); @@ -124,6 +130,6 @@ public class CommandUtilsTest { .labels("authme", "auth") .description("Base") .detailedDescription("Test base command.") - .executableCommand(mock(ExecutableCommand.class)); + .executableCommand(ExecutableCommand.class); } } diff --git a/src/test/java/fr/xephi/authme/command/PlayerCommandTest.java b/src/test/java/fr/xephi/authme/command/PlayerCommandTest.java index a4c02b93..815d8a21 100644 --- a/src/test/java/fr/xephi/authme/command/PlayerCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/PlayerCommandTest.java @@ -27,7 +27,7 @@ public class PlayerCommandTest { PlayerCommandImpl command = new PlayerCommandImpl(); // when - command.executeCommand(sender, Collections.emptyList(), mock(CommandService.class)); + command.executeCommand(sender, Collections.emptyList()); // then verify(sender).sendMessage(argThat(containsString("only for players"))); @@ -38,11 +38,10 @@ public class PlayerCommandTest { // given Player player = mock(Player.class); List arguments = Arrays.asList("arg1", "testarg2"); - CommandService service = mock(CommandService.class); PlayerCommandImpl command = new PlayerCommandImpl(); // when - command.executeCommand(player, arguments, service); + command.executeCommand(player, arguments); // then verify(player, times(1)).sendMessage("testarg2"); @@ -55,7 +54,7 @@ public class PlayerCommandTest { PlayerCommandWithAlt command = new PlayerCommandWithAlt(); // when - command.executeCommand(sender, Collections.emptyList(), mock(CommandService.class)); + command.executeCommand(sender, Collections.emptyList()); // then verify(sender, times(1)).sendMessage(argThat(containsString("use /authme test instead"))); @@ -64,14 +63,14 @@ public class PlayerCommandTest { private static class PlayerCommandImpl extends PlayerCommand { @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { player.sendMessage(arguments.get(1)); } } private static class PlayerCommandWithAlt extends PlayerCommand { @Override - public void runCommand(Player player, List arguments, CommandService commandService) { + public void runCommand(Player player, List arguments) { throw new IllegalStateException("Should not be called"); } @Override diff --git a/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java b/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java index 3beb7693..28536997 100644 --- a/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java +++ b/src/test/java/fr/xephi/authme/command/TestCommandsUtil.java @@ -1,9 +1,10 @@ package fr.xephi.authme.command; import fr.xephi.authme.command.executable.HelpCommand; -import fr.xephi.authme.permission.DefaultPermission; +import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PlayerPermission; +import org.bukkit.command.CommandSender; import java.util.Collection; import java.util.List; @@ -12,7 +13,6 @@ import java.util.Set; import static com.google.common.collect.Sets.newHashSet; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static org.mockito.Mockito.mock; /** * Util class for generating and retrieving test commands. @@ -29,22 +29,24 @@ public final class TestCommandsUtil { */ public static Set generateCommands() { // Register /authme - CommandDescription authMeBase = createCommand(null, null, singletonList("authme")); + CommandDescription authMeBase = createCommand(null, null, singletonList("authme"), ExecutableCommand.class); // Register /authme login - createCommand(PlayerPermission.LOGIN, authMeBase, singletonList("login"), newArgument("password", false)); + createCommand(PlayerPermission.LOGIN, authMeBase, singletonList("login"), + TestLoginCommand.class, newArgument("password", false)); // Register /authme register , aliases: /authme reg, /authme r - createCommand(PlayerPermission.LOGIN, authMeBase, asList("register", "reg", "r"), + createCommand(PlayerPermission.LOGIN, authMeBase, asList("register", "reg", "r"), TestRegisterCommand.class, newArgument("password", false), newArgument("confirmation", false)); // Register /email [player] - CommandDescription emailBase = createCommand(null, null, singletonList("email"), newArgument("player", true)); + CommandDescription emailBase = createCommand(null, null, singletonList("email"), ExecutableCommand.class, + newArgument("player", true)); // Register /email helptest -- use only to test for help command arguments special case - CommandDescription.builder().parent(emailBase).labels("helptest").executableCommand(mock(HelpCommand.class)) + CommandDescription.builder().parent(emailBase).labels("helptest").executableCommand(HelpCommand.class) .description("test").detailedDescription("Test.").withArgument("Query", "", false).build(); // Register /unregister , alias: /unreg - CommandDescription unregisterBase = createCommand(null, null, asList("unregister", "unreg"), - newArgument("player", false)); + CommandDescription unregisterBase = createCommand(AdminPermission.UNREGISTER, null, + asList("unregister", "unreg"), TestUnregisterCommand.class, newArgument("player", false)); return newHashSet(authMeBase, emailBase, unregisterBase); } @@ -83,22 +85,15 @@ public final class TestCommandsUtil { /** Shortcut command to initialize a new test command. */ private static CommandDescription createCommand(PermissionNode permission, CommandDescription parent, - List labels, CommandArgumentDescription... arguments) { - PermissionNode[] notNullPermission; - if (permission != null) { - notNullPermission = new PermissionNode[1]; - notNullPermission[0] = permission; - } else { - notNullPermission = new PermissionNode[0]; - } - + List labels, Class commandClass, + CommandArgumentDescription... arguments) { CommandDescription.CommandBuilder command = CommandDescription.builder() .labels(labels) .parent(parent) - .permissions(DefaultPermission.OP_ONLY, notNullPermission) + .permission(permission) .description(labels.get(0) + " cmd") .detailedDescription("'" + labels.get(0) + "' test command") - .executableCommand(mock(ExecutableCommand.class)); + .executableCommand(commandClass); if (arguments != null && arguments.length > 0) { for (CommandArgumentDescription argument : arguments) { @@ -114,4 +109,25 @@ public final class TestCommandsUtil { return new CommandArgumentDescription(label, "'" + label + "' argument description", isOptional); } + public static class TestLoginCommand implements ExecutableCommand { + @Override + public void executeCommand(CommandSender sender, List arguments) { + // noop + } + } + + public static class TestRegisterCommand implements ExecutableCommand { + @Override + public void executeCommand(CommandSender sender, List arguments) { + // noop + } + } + + public static class TestUnregisterCommand implements ExecutableCommand { + @Override + public void executeCommand(CommandSender sender, List arguments) { + // noop + } + } + } diff --git a/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java new file mode 100644 index 00000000..143ae26b --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java @@ -0,0 +1,158 @@ +package fr.xephi.authme.command.executable; + +import fr.xephi.authme.command.CommandDescription; +import fr.xephi.authme.command.CommandMapper; +import fr.xephi.authme.command.FoundCommandResult; +import fr.xephi.authme.command.help.HelpProvider; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Collections; +import java.util.List; + +import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS; +import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; +import static fr.xephi.authme.command.FoundResultStatus.SUCCESS; +import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link HelpCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class HelpCommandTest { + + @InjectMocks + private HelpCommand command; + + @Mock + private CommandMapper commandMapper; + + @Mock + private HelpProvider helpProvider; + + @Test + public void shouldHandleMissingBaseCommand() { + // given + List arguments = asList("some", "command"); + CommandSender sender = mock(CommandSender.class); + FoundCommandResult foundCommandResult = new FoundCommandResult(null, null, null, 0.0, MISSING_BASE_COMMAND); + given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult); + + // when + command.executeCommand(sender, arguments); + + // then + verify(sender).sendMessage(argThat(containsString("Could not get base command"))); + verifyZeroInteractions(helpProvider); + } + + @Test + public void shouldHandleWrongCommandWithSuggestion() { + // given + List arguments = asList("authme", "ragister", "test"); + CommandSender sender = mock(CommandSender.class); + CommandDescription description = newCommandDescription("authme", "register"); + FoundCommandResult foundCommandResult = new FoundCommandResult(description, asList("authme", "ragister"), + singletonList("test"), 0.1, UNKNOWN_LABEL); + given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult); + + // when + command.executeCommand(sender, arguments); + + // then + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(sender).sendMessage(captor.capture()); + assertThat(removeColors(captor.getValue()), containsString("Assuming /authme register")); + verify(helpProvider).outputHelp(sender, foundCommandResult, HelpProvider.ALL_OPTIONS); + } + + @Test + public void shouldHandleWrongCommandWithoutSuggestion() { + List arguments = asList("authme", "ragister", "test"); + CommandSender sender = mock(CommandSender.class); + FoundCommandResult foundCommandResult = new FoundCommandResult(null, asList("authme", "ragister"), + singletonList("test"), 0.4, UNKNOWN_LABEL); + given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult); + + // when + command.executeCommand(sender, arguments); + + // then + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(sender).sendMessage(captor.capture()); + assertThat(removeColors(captor.getValue()), containsString("Unknown command")); + verifyZeroInteractions(helpProvider); + } + + @Test + public void shouldShowChildrenOfBaseCommand() { + List arguments = singletonList("authme"); + CommandSender sender = mock(CommandSender.class); + CommandDescription commandDescription = mock(CommandDescription.class); + given(commandDescription.getLabelCount()).willReturn(1); + FoundCommandResult foundCommandResult = new FoundCommandResult(commandDescription, singletonList("authme"), + Collections.emptyList(), 0.0, SUCCESS); + given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult); + + // when + command.executeCommand(sender, arguments); + + // then + verify(sender, never()).sendMessage(anyString()); + verify(helpProvider).outputHelp(sender, foundCommandResult, HelpProvider.SHOW_CHILDREN); + } + + @Test + public void shouldShowDetailedHelpForChildCommand() { + List arguments = asList("authme", "getpos"); + CommandSender sender = mock(CommandSender.class); + CommandDescription commandDescription = mock(CommandDescription.class); + given(commandDescription.getLabelCount()).willReturn(2); + FoundCommandResult foundCommandResult = new FoundCommandResult(commandDescription, asList("authme", "getpos"), + Collections.emptyList(), 0.0, INCORRECT_ARGUMENTS); + given(commandMapper.mapPartsToCommand(sender, arguments)).willReturn(foundCommandResult); + + // when + command.executeCommand(sender, arguments); + + // then + verify(sender, never()).sendMessage(anyString()); + verify(helpProvider).outputHelp(sender, foundCommandResult, HelpProvider.ALL_OPTIONS); + } + + private static CommandDescription newCommandDescription(String... labels) { + CommandDescription parent = null; + // iterate through the labels backwards so we can set the parent + for (String label : labels) { + CommandDescription description = mock(CommandDescription.class); + given(description.getParent()).willReturn(parent); + given(description.getLabels()).willReturn(singletonList(label)); + parent = description; + } + return parent; + } + + private static String removeColors(String str) { + for (ChatColor color : ChatColor.values()) { + str = str.replace(color.toString(), ""); + } + return str; + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java index 5a837280..09615751 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/AccountsCommandTest.java @@ -4,10 +4,14 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; -import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; @@ -22,38 +26,34 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * Test for {@link AccountsCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class AccountsCommandTest { + @InjectMocks private AccountsCommand command; - private CommandSender sender; + @Mock private CommandService service; + @Mock private DataSource dataSource; - - @Before - public void setUpMocks() { - command = new AccountsCommand(); - sender = mock(CommandSender.class); - dataSource = mock(DataSource.class); - service = mock(CommandService.class); - when(service.getDataSource()).thenReturn(dataSource); - } + @Mock + private BukkitService bukkitService; @Test public void shouldGetAccountsOfCurrentUser() { // given + CommandSender sender = mock(CommandSender.class); given(sender.getName()).willReturn("Tester"); List arguments = Collections.emptyList(); given(dataSource.getAuth("tester")).willReturn(authWithIp("123.45.67.89")); given(dataSource.getAllAuthsByIp("123.45.67.89")).willReturn(Arrays.asList("Toaster", "Pester")); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then String[] messages = getMessagesSentToSender(sender, 2); @@ -64,12 +64,13 @@ public class AccountsCommandTest { @Test public void shouldReturnUnknownUserForNullAuth() { // given + CommandSender sender = mock(CommandSender.class); List arguments = Collections.singletonList("SomeUser"); given(dataSource.getAuth("someuser")).willReturn(null); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then verify(service).send(sender, MessageKey.UNKNOWN_USER); @@ -79,13 +80,14 @@ public class AccountsCommandTest { @Test public void shouldReturnUnregisteredMessageForEmptyAuthList() { // given + CommandSender sender = mock(CommandSender.class); List arguments = Collections.singletonList("SomeUser"); given(dataSource.getAuth("someuser")).willReturn(mock(PlayerAuth.class)); given(dataSource.getAllAuthsByIp(anyString())).willReturn(Collections.emptyList()); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then verify(service).send(sender, MessageKey.USER_NOT_REGISTERED); @@ -95,13 +97,14 @@ public class AccountsCommandTest { @Test public void shouldReturnSingleAccountMessage() { // given + CommandSender sender = mock(CommandSender.class); List arguments = Collections.singletonList("SomeUser"); given(dataSource.getAuth("someuser")).willReturn(authWithIp("56.78.90.123")); given(dataSource.getAllAuthsByIp("56.78.90.123")).willReturn(Collections.singletonList("SomeUser")); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then String[] messages = getMessagesSentToSender(sender, 1); @@ -114,12 +117,13 @@ public class AccountsCommandTest { @Test public void shouldReturnIpUnknown() { // given + CommandSender sender = mock(CommandSender.class); List arguments = Collections.singletonList("123.45.67.89"); given(dataSource.getAllAuthsByIp("123.45.67.89")).willReturn(Collections.emptyList()); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then String[] messages = getMessagesSentToSender(sender, 1); @@ -129,12 +133,13 @@ public class AccountsCommandTest { @Test public void shouldReturnSingleAccountForIpQuery() { // given + CommandSender sender = mock(CommandSender.class); List arguments = Collections.singletonList("24.24.48.48"); given(dataSource.getAllAuthsByIp("24.24.48.48")).willReturn(Collections.singletonList("SomeUser")); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then String[] messages = getMessagesSentToSender(sender, 1); @@ -144,12 +149,13 @@ public class AccountsCommandTest { @Test public void shouldReturnAccountListForIpQuery() { // given + CommandSender sender = mock(CommandSender.class); List arguments = Collections.singletonList("98.76.41.122"); given(dataSource.getAllAuthsByIp("98.76.41.122")).willReturn(Arrays.asList("Tester", "Lester", "Taster")); // when - command.executeCommand(sender, arguments, service); - runInnerRunnable(service); + command.executeCommand(sender, arguments); + runInnerRunnable(bukkitService); // then String[] messages = getMessagesSentToSender(sender, 2); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/AuthMeCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/AuthMeCommandTest.java index c1c0c6e3..f17f75fe 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/AuthMeCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/AuthMeCommandTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import org.bukkit.command.CommandSender; import org.junit.Test; @@ -24,10 +23,9 @@ public class AuthMeCommandTest { // given ExecutableCommand command = new AuthMeCommand(); CommandSender sender = mock(CommandSender.class); - CommandService service = mock(CommandService.class); // when - command.executeCommand(sender, Collections. emptyList(), service); + command.executeCommand(sender, Collections.emptyList()); // then ArgumentCaptor messagesCaptor = ArgumentCaptor.forClass(String.class); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommandTest.java index b4f42886..e4aea7ed 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommandTest.java @@ -4,15 +4,18 @@ import fr.xephi.authme.TestHelper; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -24,6 +27,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link ChangePasswordAdminCommand}. @@ -31,9 +35,27 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class ChangePasswordAdminCommandTest { + @InjectMocks + private ChangePasswordAdminCommand command; + @Mock private CommandService service; + @Mock + private PasswordSecurity passwordSecurity; + + @Mock + private DataSource dataSource; + + @Mock + private PlayerCache playerCache; + + @Mock + private BukkitService bukkitService; + + @Mock + private ValidationService validationService; + @BeforeClass public static void setUpLogger() { TestHelper.setupLogger(); @@ -42,37 +64,32 @@ public class ChangePasswordAdminCommandTest { @Test public void shouldRejectInvalidPassword() { // given - ExecutableCommand command = new ChangePasswordAdminCommand(); CommandSender sender = mock(CommandSender.class); - given(service.validatePassword("Bobby", "bobby")).willReturn(MessageKey.PASSWORD_IS_USERNAME_ERROR); + given(validationService.validatePassword("Bobby", "bobby")).willReturn( + new ValidationResult(MessageKey.PASSWORD_IS_USERNAME_ERROR)); // when - command.executeCommand(sender, Arrays.asList("bobby", "Bobby"), service); + command.executeCommand(sender, Arrays.asList("bobby", "Bobby")); // then - verify(service).validatePassword("Bobby", "bobby"); - verify(service).send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); - verify(service, never()).getDataSource(); + verify(validationService).validatePassword("Bobby", "bobby"); + verify(service).send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR, new String[0]); + verifyZeroInteractions(dataSource); } @Test public void shouldRejectCommandForUnknownUser() { // given - ExecutableCommand command = new ChangePasswordAdminCommand(); CommandSender sender = mock(CommandSender.class); String player = "player"; - - PlayerCache playerCache = mock(PlayerCache.class); + String password = "password"; given(playerCache.isAuthenticated(player)).willReturn(false); - given(service.getPlayerCache()).willReturn(playerCache); - - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(player)).willReturn(null); - given(service.getDataSource()).willReturn(dataSource); + given(validationService.validatePassword(password, player)).willReturn(new ValidationResult()); // when - command.executeCommand(sender, Arrays.asList(player, "password"), service); - runInnerRunnable(service); + command.executeCommand(sender, Arrays.asList(player, password)); + runInnerRunnable(bukkitService); // then verify(service).send(sender, MessageKey.UNKNOWN_USER); @@ -82,33 +99,25 @@ public class ChangePasswordAdminCommandTest { @Test public void shouldUpdatePasswordOfLoggedInUser() { // given - ExecutableCommand command = new ChangePasswordAdminCommand(); CommandSender sender = mock(CommandSender.class); - String player = "my_user12"; String password = "passPass"; PlayerAuth auth = mock(PlayerAuth.class); - PlayerCache playerCache = mock(PlayerCache.class); given(playerCache.isAuthenticated(player)).willReturn(true); given(playerCache.getAuth(player)).willReturn(auth); - given(service.getPlayerCache()).willReturn(playerCache); - PasswordSecurity passwordSecurity = mock(PasswordSecurity.class); HashedPassword hashedPassword = mock(HashedPassword.class); given(passwordSecurity.computeHash(password, player)).willReturn(hashedPassword); - given(service.getPasswordSecurity()).willReturn(passwordSecurity); - - DataSource dataSource = mock(DataSource.class); given(dataSource.updatePassword(auth)).willReturn(true); - given(service.getDataSource()).willReturn(dataSource); + given(validationService.validatePassword(password, player)).willReturn(new ValidationResult()); // when - command.executeCommand(sender, Arrays.asList(player, password), service); - runInnerRunnable(service); + command.executeCommand(sender, Arrays.asList(player, password)); + runInnerRunnable(bukkitService); // then - verify(service).validatePassword(password, player); + verify(validationService).validatePassword(password, player); verify(service).send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); verify(passwordSecurity).computeHash(password, player); verify(auth).setPassword(hashedPassword); @@ -118,34 +127,25 @@ public class ChangePasswordAdminCommandTest { @Test public void shouldUpdatePasswordOfOfflineUser() { // given - ExecutableCommand command = new ChangePasswordAdminCommand(); CommandSender sender = mock(CommandSender.class); - String player = "my_user12"; String password = "passPass"; PlayerAuth auth = mock(PlayerAuth.class); - - PlayerCache playerCache = mock(PlayerCache.class); given(playerCache.isAuthenticated(player)).willReturn(false); - given(service.getPlayerCache()).willReturn(playerCache); - - DataSource dataSource = mock(DataSource.class); given(dataSource.isAuthAvailable(player)).willReturn(true); given(dataSource.getAuth(player)).willReturn(auth); given(dataSource.updatePassword(auth)).willReturn(true); - given(service.getDataSource()).willReturn(dataSource); + given(validationService.validatePassword(password, player)).willReturn(new ValidationResult()); - PasswordSecurity passwordSecurity = mock(PasswordSecurity.class); HashedPassword hashedPassword = mock(HashedPassword.class); given(passwordSecurity.computeHash(password, player)).willReturn(hashedPassword); - given(service.getPasswordSecurity()).willReturn(passwordSecurity); // when - command.executeCommand(sender, Arrays.asList(player, password), service); - runInnerRunnable(service); + command.executeCommand(sender, Arrays.asList(player, password)); + runInnerRunnable(bukkitService); // then - verify(service).validatePassword(password, player); + verify(validationService).validatePassword(password, player); verify(service).send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); verify(passwordSecurity).computeHash(password, player); verify(auth).setPassword(hashedPassword); @@ -155,33 +155,24 @@ public class ChangePasswordAdminCommandTest { @Test public void shouldReportWhenSaveFailed() { // given - ExecutableCommand command = new ChangePasswordAdminCommand(); CommandSender sender = mock(CommandSender.class); - String player = "my_user12"; String password = "passPass"; PlayerAuth auth = mock(PlayerAuth.class); - - PlayerCache playerCache = mock(PlayerCache.class); given(playerCache.isAuthenticated(player)).willReturn(true); given(playerCache.getAuth(player)).willReturn(auth); - given(service.getPlayerCache()).willReturn(playerCache); + given(validationService.validatePassword(password, player)).willReturn(new ValidationResult()); - PasswordSecurity passwordSecurity = mock(PasswordSecurity.class); HashedPassword hashedPassword = mock(HashedPassword.class); given(passwordSecurity.computeHash(password, player)).willReturn(hashedPassword); - given(service.getPasswordSecurity()).willReturn(passwordSecurity); - - DataSource dataSource = mock(DataSource.class); given(dataSource.updatePassword(auth)).willReturn(false); - given(service.getDataSource()).willReturn(dataSource); // when - command.executeCommand(sender, Arrays.asList(player, password), service); - runInnerRunnable(service); + command.executeCommand(sender, Arrays.asList(player, password)); + runInnerRunnable(bukkitService); // then - verify(service).validatePassword(password, player); + verify(validationService).validatePassword(password, player); verify(service).send(sender, MessageKey.ERROR); verify(passwordSecurity).computeHash(password, player); verify(auth).setPassword(hashedPassword); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java new file mode 100644 index 00000000..5f781752 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java @@ -0,0 +1,104 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.converter.RakamakConverter; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.command.CommandSender; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link ConverterCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class ConverterCommandTest { + + @InjectMocks + private ConverterCommand command; + + @Mock + private CommandService commandService; + + @Mock + private BukkitService bukkitService; + + @Mock + private AuthMeServiceInitializer initializer; + + @Test + public void shouldHandleUnknownConversionType() { + // given + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Collections.singletonList("invalid")); + + // then + verify(commandService).send(sender, MessageKey.ERROR); + verifyNoMoreInteractions(commandService); + verifyZeroInteractions(initializer); + verifyZeroInteractions(bukkitService); + } + + @Test + public void shouldHaveUniqueNameAndClassForEachType() { + // given + ConverterCommand.ConvertType[] types = ConverterCommand.ConvertType.values(); + List names = new ArrayList<>(types.length); + List> classes = new ArrayList<>(types.length); + + // when / then + for (ConverterCommand.ConvertType type : types) { + assertThat("Name for '" + type + "' is not null", + type.getName(), not(nullValue())); + assertThat("Class for '" + type + "' is not null", + type.getConverterClass(), not(nullValue())); + assertThat("Name '" + type.getName() + "' is unique", + names, not(hasItem(type.getName()))); + assertThat("Class '" + type.getConverterClass() + "' is unique", + classes, not(hasItem(type.getConverterClass()))); + names.add(type.getName()); + classes.add(type.getConverterClass()); + } + } + + @Test + public void shouldLaunchConverterForAllTypes() { + // given + ConverterCommand.ConvertType type = ConverterCommand.ConvertType.RAKAMAK; + RakamakConverter converter = mock(RakamakConverter.class); + given(initializer.newInstance(RakamakConverter.class)).willReturn(converter); + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Collections.singletonList(type.getName())); + TestHelper.runInnerRunnable(bukkitService); + + // then + verify(converter).execute(sender); + verifyNoMoreInteractions(converter); + verify(initializer).newInstance(type.getConverterClass()); + verifyNoMoreInteractions(initializer); + } + +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommandTest.java index 0c70bd01..000c7922 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/FirstSpawnCommandTest.java @@ -1,11 +1,13 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; 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.runners.MockitoJUnitRunner; import java.util.Collections; @@ -21,21 +23,24 @@ import static org.mockito.Mockito.verify; /** * Test for {@link FirstSpawnCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class FirstSpawnCommandTest { + @InjectMocks + private FirstSpawnCommand command; + + @Mock + private SpawnLoader spawnLoader; + @Test public void shouldTeleportToFirstSpawn() { // given Location firstSpawn = mock(Location.class); - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.getFirstSpawn()).willReturn(firstSpawn); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); Player player = mock(Player.class); - ExecutableCommand command = new FirstSpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(player).teleport(firstSpawn); @@ -45,15 +50,11 @@ public class FirstSpawnCommandTest { @Test public void shouldHandleMissingFirstSpawn() { // given - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.getFirstSpawn()).willReturn(null); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); Player player = mock(Player.class); - ExecutableCommand command = new FirstSpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(player).sendMessage(argThat(containsString("spawn has failed"))); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ForceLoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ForceLoginCommandTest.java index f6a01f2d..9f89fe5a 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/ForceLoginCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/ForceLoginCommandTest.java @@ -1,14 +1,14 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.process.Management; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; 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.runners.MockitoJUnitRunner; @@ -21,8 +21,8 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link ForceLoginCommand}. @@ -30,42 +30,49 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class ForceLoginCommandTest { + @InjectMocks + private ForceLoginCommand command; + @Mock - private CommandService commandService; + private Management management; + + @Mock + private PermissionsManager permissionsManager; + + @Mock + private BukkitService bukkitService; @Test public void shouldRejectOfflinePlayer() { // given String playerName = "Bobby"; Player player = mockPlayer(false, playerName); - given(commandService.getPlayer(playerName)).willReturn(player); + given(bukkitService.getPlayerExact(playerName)).willReturn(player); CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new ForceLoginCommand(); // when - command.executeCommand(sender, Collections.singletonList(playerName), commandService); + command.executeCommand(sender, Collections.singletonList(playerName)); // then - verify(commandService).getPlayer(playerName); + verify(bukkitService).getPlayerExact(playerName); verify(sender).sendMessage(argThat(equalTo("Player needs to be online!"))); - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); } @Test public void shouldRejectInexistentPlayer() { // given String playerName = "us3rname01"; - given(commandService.getPlayer(playerName)).willReturn(null); + given(bukkitService.getPlayerExact(playerName)).willReturn(null); CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new ForceLoginCommand(); // when - command.executeCommand(sender, Collections.singletonList(playerName), commandService); + command.executeCommand(sender, Collections.singletonList(playerName)); // then - verify(commandService).getPlayer(playerName); + verify(bukkitService).getPlayerExact(playerName); verify(sender).sendMessage(argThat(equalTo("Player needs to be online!"))); - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); } @Test @@ -73,21 +80,17 @@ public class ForceLoginCommandTest { // given String playerName = "testTest"; Player player = mockPlayer(true, playerName); - given(commandService.getPlayer(playerName)).willReturn(player); - PermissionsManager permissionsManager = mock(PermissionsManager.class); + given(bukkitService.getPlayerExact(playerName)).willReturn(player); given(permissionsManager.hasPermission(player, PlayerPermission.CAN_LOGIN_BE_FORCED)).willReturn(false); - given(commandService.getPermissionsManager()).willReturn(permissionsManager); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new ForceLoginCommand(); // when - command.executeCommand(sender, Collections.singletonList(playerName), commandService); + command.executeCommand(sender, Collections.singletonList(playerName)); // then - verify(commandService).getPlayer(playerName); + verify(bukkitService).getPlayerExact(playerName); verify(sender).sendMessage(argThat(containsString("You cannot force login the player"))); - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); } @Test @@ -95,21 +98,15 @@ public class ForceLoginCommandTest { // given String playerName = "tester23"; Player player = mockPlayer(true, playerName); - given(commandService.getPlayer(playerName)).willReturn(player); - PermissionsManager permissionsManager = mock(PermissionsManager.class); + given(bukkitService.getPlayerExact(playerName)).willReturn(player); given(permissionsManager.hasPermission(player, PlayerPermission.CAN_LOGIN_BE_FORCED)).willReturn(true); - given(commandService.getPermissionsManager()).willReturn(permissionsManager); - Management management = mock(Management.class); - given(commandService.getManagement()).willReturn(management); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new ForceLoginCommand(); // when - command.executeCommand(sender, Collections.singletonList(playerName), commandService); + command.executeCommand(sender, Collections.singletonList(playerName)); // then - verify(commandService).getPlayer(playerName); + verify(bukkitService).getPlayerExact(playerName); verify(management).performLogin(eq(player), anyString(), eq(true)); } @@ -118,22 +115,16 @@ public class ForceLoginCommandTest { // given String senderName = "tester23"; Player player = mockPlayer(true, senderName); - given(commandService.getPlayer(senderName)).willReturn(player); - PermissionsManager permissionsManager = mock(PermissionsManager.class); + given(bukkitService.getPlayerExact(senderName)).willReturn(player); given(permissionsManager.hasPermission(player, PlayerPermission.CAN_LOGIN_BE_FORCED)).willReturn(true); - given(commandService.getPermissionsManager()).willReturn(permissionsManager); - Management management = mock(Management.class); - given(commandService.getManagement()).willReturn(management); - CommandSender sender = mock(CommandSender.class); given(sender.getName()).willReturn(senderName); - ExecutableCommand command = new ForceLoginCommand(); // when - command.executeCommand(sender, Collections.emptyList(), commandService); + command.executeCommand(sender, Collections.emptyList()); // then - verify(commandService).getPlayer(senderName); + verify(bukkitService).getPlayerExact(senderName); verify(management).performLogin(eq(player), anyString(), eq(true)); } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/GetEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/GetEmailCommandTest.java index 6d232037..6afdc329 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/GetEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/GetEmailCommandTest.java @@ -2,11 +2,14 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import org.bukkit.command.CommandSender; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Collections; @@ -19,22 +22,27 @@ import static org.mockito.Mockito.verify; /** * Test for {@link GetEmailCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class GetEmailCommandTest { + @InjectMocks + private GetEmailCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private CommandService service; + @Test public void shouldReportUnknownUser() { // given String user = "myTestUser"; - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(user)).willReturn(null); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new GetEmailCommand(); // when - command.executeCommand(sender, Collections.singletonList(user), service); + command.executeCommand(sender, Collections.singletonList(user)); // then verify(service).send(sender, MessageKey.UNKNOWN_USER); @@ -47,17 +55,11 @@ public class GetEmailCommandTest { String email = "user.email@example.org"; PlayerAuth auth = mock(PlayerAuth.class); given(auth.getEmail()).willReturn(email); - - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(user)).willReturn(auth); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new GetEmailCommand(); // when - command.executeCommand(sender, Collections.singletonList(user), service); + command.executeCommand(sender, Collections.singletonList(user)); // then verify(sender).sendMessage(argThat(containsString(email))); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/GetIpCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/GetIpCommandTest.java index 7e070af6..65ed998a 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/GetIpCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/GetIpCommandTest.java @@ -1,16 +1,15 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.util.BukkitService; import org.bukkit.command.CommandSender; 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.runners.MockitoJUnitRunner; -import java.net.InetAddress; -import java.net.InetSocketAddress; import java.util.Collections; import static org.hamcrest.Matchers.allOf; @@ -27,22 +26,24 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class GetIpCommandTest { + @InjectMocks + private GetIpCommand command; + @Mock - private CommandService commandService; - @Mock - private CommandSender sender; + private BukkitService bukkitService; + @Test public void shouldGetIpOfPlayer() { // given - given(commandService.getPlayer(anyString())).willReturn(null); - ExecutableCommand command = new GetIpCommand(); + given(bukkitService.getPlayerExact(anyString())).willReturn(null); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Collections.singletonList("Testt"), commandService); + command.executeCommand(sender, Collections.singletonList("Testt")); // then - verify(commandService).getPlayer("Testt"); + verify(bukkitService).getPlayerExact("Testt"); verify(sender).sendMessage(argThat(containsString("not online"))); } @@ -52,24 +53,21 @@ public class GetIpCommandTest { String playerName = "charlie"; String ip = "123.34.56.88"; Player player = mockPlayer(playerName, ip); - given(commandService.getPlayer(playerName)).willReturn(player); - ExecutableCommand command = new GetIpCommand(); + given(bukkitService.getPlayerExact(playerName)).willReturn(player); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Collections.singletonList(playerName), commandService); + command.executeCommand(sender, Collections.singletonList(playerName)); // then - verify(commandService).getPlayer(playerName); + verify(bukkitService).getPlayerExact(playerName); verify(sender).sendMessage(argThat(allOf(containsString(playerName), containsString(ip)))); } private static Player mockPlayer(String name, String ip) { Player player = mock(Player.class); given(player.getName()).willReturn(name); - InetAddress inetAddress = mock(InetAddress.class); - given(inetAddress.getHostAddress()).willReturn(ip); - InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, 8093); - given(player.getAddress()).willReturn(inetSocketAddress); + TestHelper.mockPlayerIp(player, ip); return player; } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/LastLoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/LastLoginCommandTest.java index d1e58e92..d42996ee 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/LastLoginCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/LastLoginCommandTest.java @@ -2,12 +2,15 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import org.bukkit.command.CommandSender; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Collections; import java.util.Date; @@ -23,26 +26,31 @@ import static org.mockito.Mockito.verify; /** * Test for {@link LastLoginCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class LastLoginCommandTest { private static final long HOUR_IN_MSEC = 3600 * 1000; private static final long DAY_IN_MSEC = 24 * HOUR_IN_MSEC; + @InjectMocks + private LastLoginCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private CommandService service; + + @Test public void shouldRejectNonExistentUser() { // given String player = "tester"; - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(player)).willReturn(null); - - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new LastLoginCommand(); // when - command.executeCommand(sender, Collections.singletonList(player), service); + command.executeCommand(sender, Collections.singletonList(player)); // then verify(dataSource).getAuth(player); @@ -58,17 +66,11 @@ public class LastLoginCommandTest { PlayerAuth auth = mock(PlayerAuth.class); given(auth.getLastLogin()).willReturn(lastLogin); given(auth.getIp()).willReturn("123.45.66.77"); - - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(player)).willReturn(auth); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new LastLoginCommand(); // when - command.executeCommand(sender, Collections.singletonList(player), service); + command.executeCommand(sender, Collections.singletonList(player)); // then verify(dataSource).getAuth(player); @@ -93,17 +95,10 @@ public class LastLoginCommandTest { PlayerAuth auth = mock(PlayerAuth.class); given(auth.getLastLogin()).willReturn(lastLogin); given(auth.getIp()).willReturn("123.45.66.77"); - - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(name)).willReturn(auth); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - - - ExecutableCommand command = new LastLoginCommand(); // when - command.executeCommand(sender, Collections.emptyList(), service); + command.executeCommand(sender, Collections.emptyList()); // then verify(dataSource).getAuth(name); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommandTest.java index bcce0342..e3a79db8 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/PurgeLastPositionCommandTest.java @@ -2,11 +2,14 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import org.bukkit.command.CommandSender; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; @@ -20,24 +23,29 @@ import static org.mockito.Mockito.verify; /** * Test for {@link PurgeLastPositionCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class PurgeLastPositionCommandTest { + @InjectMocks + private PurgeLastPositionCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private CommandService service; + + @Test public void shouldPurgeLastPosOfUser() { // given String player = "_Bobby"; PlayerAuth auth = mock(PlayerAuth.class); - - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(player)).willReturn(auth); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new PurgeLastPositionCommand(); // when - command.executeCommand(sender, Collections.singletonList(player), service); + command.executeCommand(sender, Collections.singletonList(player)); // then verify(dataSource).getAuth(player); @@ -51,17 +59,11 @@ public class PurgeLastPositionCommandTest { String player = "_Bobby"; CommandSender sender = mock(CommandSender.class); given(sender.getName()).willReturn(player); - PlayerAuth auth = mock(PlayerAuth.class); - DataSource dataSource = mock(DataSource.class); given(dataSource.getAuth(player)).willReturn(auth); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - - ExecutableCommand command = new PurgeLastPositionCommand(); // when - command.executeCommand(sender, Collections.emptyList(), service); + command.executeCommand(sender, Collections.emptyList()); // then verify(dataSource).getAuth(player); @@ -72,16 +74,11 @@ public class PurgeLastPositionCommandTest { @Test public void shouldHandleNonExistentUser() { // given - DataSource dataSource = mock(DataSource.class); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - - ExecutableCommand command = new PurgeLastPositionCommand(); - CommandSender sender = mock(CommandSender.class); String name = "invalidPlayer"; + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Collections.singletonList(name), service); + command.executeCommand(sender, Collections.singletonList(name)); // then verify(dataSource).getAuth(name); @@ -94,17 +91,11 @@ public class PurgeLastPositionCommandTest { PlayerAuth auth1 = mock(PlayerAuth.class); PlayerAuth auth2 = mock(PlayerAuth.class); PlayerAuth auth3 = mock(PlayerAuth.class); - - DataSource dataSource = mock(DataSource.class); given(dataSource.getAllAuths()).willReturn(Arrays.asList(auth1, auth2, auth3)); - CommandService service = mock(CommandService.class); - given(service.getDataSource()).willReturn(dataSource); - - ExecutableCommand command = new PurgeLastPositionCommand(); CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Collections.singletonList("*"), service); + command.executeCommand(sender, Collections.singletonList("*")); // then verify(dataSource).getAllAuths(); 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 d2c7fd1e..d6d0924e 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 @@ -1,32 +1,36 @@ package fr.xephi.authme.command.executable.authme; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import java.util.Arrays; - +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import fr.xephi.authme.TestHelper; -import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; +import java.util.Arrays; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** * Test for {@link RegisterAdminCommand}. @@ -34,11 +38,24 @@ import fr.xephi.authme.security.crypts.HashedPassword; @RunWith(MockitoJUnitRunner.class) public class RegisterAdminCommandTest { + @InjectMocks + private RegisterAdminCommand command; + @Mock - private CommandSender sender; + private PasswordSecurity passwordSecurity; + + @Mock + private DataSource dataSource; + + @Mock + private BukkitService bukkitService; + @Mock private CommandService commandService; + @Mock + private ValidationService validationService; + @BeforeClass public static void setUpLogger() { TestHelper.setupLogger(); @@ -49,16 +66,17 @@ public class RegisterAdminCommandTest { // given String user = "tester"; String password = "myPassword"; - given(commandService.validatePassword(password, user)).willReturn(MessageKey.INVALID_PASSWORD_LENGTH); - ExecutableCommand command = new RegisterAdminCommand(); + given(validationService.validatePassword(password, user)) + .willReturn(new ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH)); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Arrays.asList(user, password), commandService); + command.executeCommand(sender, Arrays.asList(user, password)); // then - verify(commandService).validatePassword(password, user); - verify(commandService).send(sender, MessageKey.INVALID_PASSWORD_LENGTH); - verify(commandService, never()).runTaskAsynchronously(any(Runnable.class)); + verify(validationService).validatePassword(password, user); + verify(commandService).send(sender, MessageKey.INVALID_PASSWORD_LENGTH, new String[0]); + verify(bukkitService, never()).runTaskAsynchronously(any(Runnable.class)); } @Test @@ -66,18 +84,16 @@ public class RegisterAdminCommandTest { // given String user = "my_name55"; String password = "@some-pass@"; - given(commandService.validatePassword(password, user)).willReturn(null); - DataSource dataSource = mock(DataSource.class); + given(validationService.validatePassword(password, user)).willReturn(new ValidationResult()); given(dataSource.isAuthAvailable(user)).willReturn(true); - given(commandService.getDataSource()).willReturn(dataSource); - ExecutableCommand command = new RegisterAdminCommand(); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Arrays.asList(user, password), commandService); - TestHelper.runInnerRunnable(commandService); + command.executeCommand(sender, Arrays.asList(user, password)); + TestHelper.runInnerRunnable(bukkitService); // then - verify(commandService).validatePassword(password, user); + verify(validationService).validatePassword(password, user); verify(commandService).send(sender, MessageKey.NAME_ALREADY_REGISTERED); verify(dataSource, never()).saveAuth(any(PlayerAuth.class)); } @@ -87,23 +103,19 @@ public class RegisterAdminCommandTest { // given String user = "test-test"; String password = "afdjhfkt"; - given(commandService.validatePassword(password, user)).willReturn(null); - DataSource dataSource = mock(DataSource.class); + given(validationService.validatePassword(password, user)).willReturn(new ValidationResult()); given(dataSource.isAuthAvailable(user)).willReturn(false); given(dataSource.saveAuth(any(PlayerAuth.class))).willReturn(false); - given(commandService.getDataSource()).willReturn(dataSource); - PasswordSecurity passwordSecurity = mock(PasswordSecurity.class); HashedPassword hashedPassword = new HashedPassword("235sdf4w5udsgf"); given(passwordSecurity.computeHash(password, user)).willReturn(hashedPassword); - given(commandService.getPasswordSecurity()).willReturn(passwordSecurity); - ExecutableCommand command = new RegisterAdminCommand(); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Arrays.asList(user, password), commandService); - TestHelper.runInnerRunnable(commandService); + command.executeCommand(sender, Arrays.asList(user, password)); + TestHelper.runInnerRunnable(bukkitService); // then - verify(commandService).validatePassword(password, user); + verify(validationService).validatePassword(password, user); verify(commandService).send(sender, MessageKey.ERROR); ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); verify(dataSource).saveAuth(captor.capture()); @@ -115,24 +127,20 @@ public class RegisterAdminCommandTest { // given String user = "someone"; String password = "Al1O3P49S5%"; - given(commandService.validatePassword(password, user)).willReturn(null); - DataSource dataSource = mock(DataSource.class); + given(validationService.validatePassword(password, user)).willReturn(new ValidationResult()); given(dataSource.isAuthAvailable(user)).willReturn(false); given(dataSource.saveAuth(any(PlayerAuth.class))).willReturn(true); - given(commandService.getDataSource()).willReturn(dataSource); - PasswordSecurity passwordSecurity = mock(PasswordSecurity.class); HashedPassword hashedPassword = new HashedPassword("$aea2345EW235dfsa@#R%987048"); given(passwordSecurity.computeHash(password, user)).willReturn(hashedPassword); - given(commandService.getPasswordSecurity()).willReturn(passwordSecurity); - given(commandService.getPlayer(user)).willReturn(null); - ExecutableCommand command = new RegisterAdminCommand(); + given(bukkitService.getPlayerExact(user)).willReturn(null); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Arrays.asList(user, password), commandService); - TestHelper.runInnerRunnable(commandService); + command.executeCommand(sender, Arrays.asList(user, password)); + TestHelper.runInnerRunnable(bukkitService); // then - verify(commandService).validatePassword(password, user); + verify(validationService).validatePassword(password, user); verify(commandService).send(sender, MessageKey.REGISTER_SUCCESS); ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); verify(dataSource).saveAuth(captor.capture()); @@ -145,28 +153,28 @@ public class RegisterAdminCommandTest { // given String user = "someone"; String password = "Al1O3P49S5%"; - given(commandService.validatePassword(password, user)).willReturn(null); - DataSource dataSource = mock(DataSource.class); + given(validationService.validatePassword(password, user)).willReturn(new ValidationResult()); given(dataSource.isAuthAvailable(user)).willReturn(false); given(dataSource.saveAuth(any(PlayerAuth.class))).willReturn(true); - given(commandService.getDataSource()).willReturn(dataSource); - PasswordSecurity passwordSecurity = mock(PasswordSecurity.class); HashedPassword hashedPassword = new HashedPassword("$aea2345EW235dfsa@#R%987048"); given(passwordSecurity.computeHash(password, user)).willReturn(hashedPassword); - given(commandService.getPasswordSecurity()).willReturn(passwordSecurity); - ExecutableCommand command = new RegisterAdminCommand(); + Player player = mock(Player.class); + given(bukkitService.getPlayerExact(user)).willReturn(player); + CommandSender sender = mock(CommandSender.class); // when - command.executeCommand(sender, Arrays.asList(user, password), commandService); - TestHelper.runInnerRunnable(commandService); + command.executeCommand(sender, Arrays.asList(user, password)); + TestHelper.runInnerRunnable(bukkitService); + runSyncDelayedTask(bukkitService); // then - verify(commandService).validatePassword(password, user); + verify(validationService).validatePassword(password, user); verify(commandService).send(sender, MessageKey.REGISTER_SUCCESS); ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); verify(dataSource).saveAuth(captor.capture()); assertAuthHasInfo(captor.getValue(), user, hashedPassword); verify(dataSource).setUnlogged(user); + verify(player).kickPlayer(argThat(containsString("please log in again"))); } private void assertAuthHasInfo(PlayerAuth auth, String name, HashedPassword hashedPassword) { @@ -174,4 +182,11 @@ public class RegisterAdminCommandTest { assertThat(auth.getNickname(), equalTo(name.toLowerCase())); assertThat(auth.getPassword(), equalTo(hashedPassword)); } + + private static void runSyncDelayedTask(BukkitService bukkitService) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + verify(bukkitService).scheduleSyncDelayedTask(captor.capture()); + Runnable runnable = captor.getValue(); + runnable.run(); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java index 99600c89..676c3b22 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java @@ -3,15 +3,27 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.datasource.DataSourceType; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.CommandSender; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Collections; +import static org.hamcrest.Matchers.containsString; import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.matches; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -20,46 +32,86 @@ import static org.mockito.Mockito.verify; /** * Test for {@link ReloadCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class ReloadCommandTest { + @InjectMocks + private ReloadCommand command; + + @Mock + private AuthMe authMe; + + @Mock + private AuthMeServiceInitializer initializer; + + @Mock + private NewSetting settings; + + @Mock + private DataSource dataSource; + + @Mock + private CommandService commandService; + @BeforeClass public static void setUpLogger() { TestHelper.setupLogger(); } - @Test - public void shouldReload() throws Exception { - // given - AuthMe authMe = mock(AuthMe.class); - CommandService service = mock(CommandService.class); - given(service.getAuthMe()).willReturn(authMe); - CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new ReloadCommand(); - - // when - command.executeCommand(sender, Collections.emptyList(), service); - - // then - verify(authMe).reload(); - verify(service).send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); + @Before + public void setDefaultSettings() { + // Mock properties retrieved by ConsoleLogger + given(settings.getProperty(SecuritySettings.REMOVE_SPAM_FROM_CONSOLE)).willReturn(false); + given(settings.getProperty(SecuritySettings.USE_LOGGING)).willReturn(false); } @Test - public void shouldHandleReloadError() throws Exception { + public void shouldReload() { // given - AuthMe authMe = mock(AuthMe.class); - doThrow(IllegalStateException.class).when(authMe).reload(); - CommandService service = mock(CommandService.class); - given(service.getAuthMe()).willReturn(authMe); CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new ReloadCommand(); + given(settings.getProperty(DatabaseSettings.BACKEND)).willReturn(DataSourceType.MYSQL); + given(dataSource.getType()).willReturn(DataSourceType.MYSQL); // when - command.executeCommand(sender, Collections.emptyList(), service); + command.executeCommand(sender, Collections.emptyList()); // then - verify(authMe).reload(); + verify(settings).reload(); + verify(initializer).performReloadOnServices(); + verify(commandService).send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); + } + + @Test + public void shouldHandleReloadError() { + // given + CommandSender sender = mock(CommandSender.class); + doThrow(IllegalStateException.class).when(initializer).performReloadOnServices(); + given(settings.getProperty(DatabaseSettings.BACKEND)).willReturn(DataSourceType.MYSQL); + given(dataSource.getType()).willReturn(DataSourceType.MYSQL); + + // when + command.executeCommand(sender, Collections.emptyList()); + + // then + verify(settings).reload(); + verify(initializer).performReloadOnServices(); verify(sender).sendMessage(matches("Error occurred.*")); verify(authMe).stopOrUnload(); } + + @Test + public void shouldIssueWarningForChangedDatasourceSetting() { + // given + CommandSender sender = mock(CommandSender.class); + given(settings.getProperty(DatabaseSettings.BACKEND)).willReturn(DataSourceType.MYSQL); + given(dataSource.getType()).willReturn(DataSourceType.SQLITE); + + // when + command.executeCommand(sender, Collections.emptyList()); + + // then + verify(settings).reload(); + verify(initializer).performReloadOnServices(); + verify(sender).sendMessage(argThat(containsString("cannot change database type"))); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/SetEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/SetEmailCommandTest.java new file mode 100644 index 00000000..26d11844 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/SetEmailCommandTest.java @@ -0,0 +1,188 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.command.CommandSender; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; + +import static fr.xephi.authme.TestHelper.runInnerRunnable; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link SetEmailCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class SetEmailCommandTest { + + @InjectMocks + private SetEmailCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private CommandService commandService; + + @Mock + private PlayerCache playerCache; + + @Mock + private BukkitService bukkitService; + + @Test + public void shouldRejectInvalidMail() { + // given + String user = "somebody"; + String email = "some.test@example.org"; + given(commandService.validateEmail(email)).willReturn(false); + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Arrays.asList(user, email)); + + // then + verify(commandService).validateEmail(email); + verify(commandService).send(sender, MessageKey.INVALID_EMAIL); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldHandleUnknownUser() { + // given + String user = "nonexistent"; + String email = "mail@example.com"; + given(commandService.validateEmail(email)).willReturn(true); + given(dataSource.getAuth(user)).willReturn(null); + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Arrays.asList(user, email)); + runInnerRunnable(bukkitService); + + // then + verify(commandService).validateEmail(email); + verify(dataSource).getAuth(user); + verify(commandService).send(sender, MessageKey.UNKNOWN_USER); + verifyNoMoreInteractions(dataSource); + } + + @Test + public void shouldHandleAlreadyTakenEmail() { + // given + String user = "someone"; + String email = "mail@example.com"; + given(commandService.validateEmail(email)).willReturn(true); + PlayerAuth auth = mock(PlayerAuth.class); + given(dataSource.getAuth(user)).willReturn(auth); + CommandSender sender = mock(CommandSender.class); + given(commandService.isEmailFreeForRegistration(email, sender)).willReturn(false); + + // when + command.executeCommand(sender, Arrays.asList(user, email)); + runInnerRunnable(bukkitService); + + // then + verify(commandService).validateEmail(email); + verify(dataSource).getAuth(user); + verify(commandService).isEmailFreeForRegistration(email, sender); + verify(commandService).send(sender, MessageKey.EMAIL_ALREADY_USED_ERROR); + verifyNoMoreInteractions(dataSource); + verifyZeroInteractions(auth); + } + + @Test + public void shouldHandlePersistenceError() { + // given + String user = "Bobby"; + String email = "new-addr@example.org"; + given(commandService.validateEmail(email)).willReturn(true); + PlayerAuth auth = mock(PlayerAuth.class); + given(dataSource.getAuth(user)).willReturn(auth); + CommandSender sender = mock(CommandSender.class); + given(commandService.isEmailFreeForRegistration(email, sender)).willReturn(true); + given(dataSource.updateEmail(auth)).willReturn(false); + + // when + command.executeCommand(sender, Arrays.asList(user, email)); + runInnerRunnable(bukkitService); + + // then + verify(commandService).validateEmail(email); + verify(dataSource).getAuth(user); + verify(commandService).isEmailFreeForRegistration(email, sender); + verify(commandService).send(sender, MessageKey.ERROR); + verify(dataSource).updateEmail(auth); + verifyNoMoreInteractions(dataSource); + } + + @Test + public void shouldUpdateEmail() { + // given + String user = "Bobby"; + String email = "new-addr@example.org"; + given(commandService.validateEmail(email)).willReturn(true); + PlayerAuth auth = mock(PlayerAuth.class); + given(dataSource.getAuth(user)).willReturn(auth); + CommandSender sender = mock(CommandSender.class); + given(commandService.isEmailFreeForRegistration(email, sender)).willReturn(true); + given(dataSource.updateEmail(auth)).willReturn(true); + given(playerCache.getAuth(user)).willReturn(null); + + // when + command.executeCommand(sender, Arrays.asList(user, email)); + runInnerRunnable(bukkitService); + + // then + verify(commandService).validateEmail(email); + verify(dataSource).getAuth(user); + verify(commandService).isEmailFreeForRegistration(email, sender); + verify(commandService).send(sender, MessageKey.EMAIL_CHANGED_SUCCESS); + verify(dataSource).updateEmail(auth); + verify(playerCache, never()).updatePlayer(any(PlayerAuth.class)); + verifyNoMoreInteractions(dataSource); + } + + @Test + public void shouldUpdateEmailAndPlayerCache() { + // given + String user = "Bobby"; + String email = "new-addr@example.org"; + given(commandService.validateEmail(email)).willReturn(true); + PlayerAuth auth = mock(PlayerAuth.class); + given(dataSource.getAuth(user)).willReturn(auth); + CommandSender sender = mock(CommandSender.class); + given(commandService.isEmailFreeForRegistration(email, sender)).willReturn(true); + given(dataSource.updateEmail(auth)).willReturn(true); + given(playerCache.getAuth(user)).willReturn(mock(PlayerAuth.class)); + + // when + command.executeCommand(sender, Arrays.asList(user, email)); + runInnerRunnable(bukkitService); + + // then + verify(commandService).validateEmail(email); + verify(dataSource).getAuth(user); + verify(commandService).isEmailFreeForRegistration(email, sender); + verify(commandService).send(sender, MessageKey.EMAIL_CHANGED_SUCCESS); + verify(dataSource).updateEmail(auth); + verify(playerCache).updatePlayer(auth); + verifyNoMoreInteractions(dataSource); + } + +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommandTest.java index 74964882..0974da72 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/SetFirstSpawnCommandTest.java @@ -1,11 +1,13 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; 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.runners.MockitoJUnitRunner; import java.util.Collections; @@ -18,24 +20,26 @@ import static org.mockito.Mockito.verify; /** * Test for {@link SetFirstSpawnCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class SetFirstSpawnCommandTest { + @InjectMocks + private SetFirstSpawnCommand command; + + @Mock + private SpawnLoader spawnLoader; + + @Test public void shouldSetFirstSpawn() { // given Player player = mock(Player.class); Location location = mock(Location.class); given(player.getLocation()).willReturn(location); - - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.setFirstSpawn(location)).willReturn(true); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); - - ExecutableCommand command = new SetFirstSpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(spawnLoader).setFirstSpawn(location); @@ -48,16 +52,10 @@ public class SetFirstSpawnCommandTest { Player player = mock(Player.class); Location location = mock(Location.class); given(player.getLocation()).willReturn(location); - - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.setFirstSpawn(location)).willReturn(false); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); - - ExecutableCommand command = new SetFirstSpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(spawnLoader).setFirstSpawn(location); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/SetSpawnCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/SetSpawnCommandTest.java index e3e01f99..8522f997 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/SetSpawnCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/SetSpawnCommandTest.java @@ -1,11 +1,13 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; 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.runners.MockitoJUnitRunner; import java.util.Collections; @@ -18,24 +20,26 @@ import static org.mockito.Mockito.verify; /** * Test for {@link SetSpawnCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class SetSpawnCommandTest { + @InjectMocks + private SetSpawnCommand command; + + @Mock + private SpawnLoader spawnLoader; + + @Test public void shouldSetSpawn() { // given Player player = mock(Player.class); Location location = mock(Location.class); given(player.getLocation()).willReturn(location); - - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.setSpawn(location)).willReturn(true); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); - - ExecutableCommand command = new SetSpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(spawnLoader).setSpawn(location); @@ -48,16 +52,10 @@ public class SetSpawnCommandTest { Player player = mock(Player.class); Location location = mock(Location.class); given(player.getLocation()).willReturn(location); - - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.setSpawn(location)).willReturn(false); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); - - ExecutableCommand command = new SetSpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(spawnLoader).setSpawn(location); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/SpawnCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/SpawnCommandTest.java index 799d1ca4..81a7f416 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/SpawnCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/SpawnCommandTest.java @@ -1,11 +1,13 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; 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.runners.MockitoJUnitRunner; import java.util.Collections; @@ -21,21 +23,25 @@ import static org.mockito.Mockito.verify; /** * Test for {@link SpawnCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class SpawnCommandTest { + @InjectMocks + private SpawnCommand command; + + @Mock + private SpawnLoader spawnLoader; + + @Test public void shouldTeleportToSpawn() { // given Location spawn = mock(Location.class); - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.getSpawn()).willReturn(spawn); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); Player player = mock(Player.class); - ExecutableCommand command = new SpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(player).teleport(spawn); @@ -45,15 +51,11 @@ public class SpawnCommandTest { @Test public void shouldHandleMissingSpawn() { // given - SpawnLoader spawnLoader = mock(SpawnLoader.class); given(spawnLoader.getSpawn()).willReturn(null); - CommandService service = mock(CommandService.class); - given(service.getSpawnLoader()).willReturn(spawnLoader); Player player = mock(Player.class); - ExecutableCommand command = new SpawnCommand(); // when - command.executeCommand(player, Collections.emptyList(), service); + command.executeCommand(player, Collections.emptyList()); // then verify(player).sendMessage(argThat(containsString("Spawn has failed"))); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommandTest.java index 3f9767f9..9b380411 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommandTest.java @@ -1,12 +1,15 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.command.CommandMapper; import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.help.HelpProvider; import org.bukkit.command.CommandSender; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.Collections; @@ -22,20 +25,29 @@ import static org.mockito.Mockito.verify; /** * Test for {@link SwitchAntiBotCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class SwitchAntiBotCommandTest { + @InjectMocks + private SwitchAntiBotCommand command; + + @Mock + private AntiBot antiBot; + + @Mock + private CommandMapper commandMapper; + + @Mock + private HelpProvider helpProvider; + @Test public void shouldReturnAntiBotState() { // given - AntiBot antiBot = mock(AntiBot.class); given(antiBot.getAntiBotStatus()).willReturn(AntiBot.AntiBotStatus.ACTIVE); - CommandService service = mock(CommandService.class); - given(service.getAntiBot()).willReturn(antiBot); CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new SwitchAntiBotCommand(); // when - command.executeCommand(sender, Collections.emptyList(), service); + command.executeCommand(sender, Collections.emptyList()); // then verify(sender).sendMessage(argThat(containsString("status: ACTIVE"))); @@ -44,14 +56,10 @@ public class SwitchAntiBotCommandTest { @Test public void shouldActivateAntiBot() { // given - AntiBot antiBot = mock(AntiBot.class); - CommandService service = mock(CommandService.class); - given(service.getAntiBot()).willReturn(antiBot); CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new SwitchAntiBotCommand(); // when - command.executeCommand(sender, Collections.singletonList("on"), service); + command.executeCommand(sender, Collections.singletonList("on")); // then verify(antiBot).overrideAntiBotStatus(true); @@ -61,14 +69,10 @@ public class SwitchAntiBotCommandTest { @Test public void shouldDeactivateAntiBot() { // given - AntiBot antiBot = mock(AntiBot.class); - CommandService service = mock(CommandService.class); - given(service.getAntiBot()).willReturn(antiBot); CommandSender sender = mock(CommandSender.class); - ExecutableCommand command = new SwitchAntiBotCommand(); // when - command.executeCommand(sender, Collections.singletonList("Off"), service); + command.executeCommand(sender, Collections.singletonList("Off")); // then verify(antiBot).overrideAntiBotStatus(false); @@ -79,21 +83,15 @@ public class SwitchAntiBotCommandTest { public void shouldShowHelpForUnknownState() { // given CommandSender sender = mock(CommandSender.class); - - AntiBot antiBot = mock(AntiBot.class); FoundCommandResult foundCommandResult = mock(FoundCommandResult.class); - CommandService service = mock(CommandService.class); - given(service.getAntiBot()).willReturn(antiBot); - given(service.mapPartsToCommand(sender, asList("authme", "antibot"))).willReturn(foundCommandResult); - - ExecutableCommand command = new SwitchAntiBotCommand(); + given(commandMapper.mapPartsToCommand(sender, asList("authme", "antibot"))).willReturn(foundCommandResult); // when - command.executeCommand(sender, Collections.singletonList("wrong"), service); + command.executeCommand(sender, Collections.singletonList("wrong")); // then verify(antiBot, never()).overrideAntiBotStatus(anyBoolean()); verify(sender).sendMessage(argThat(containsString("Invalid"))); - verify(service).outputHelp(sender, foundCommandResult, HelpProvider.SHOW_ARGUMENTS); + verify(helpProvider).outputHelp(sender, foundCommandResult, HelpProvider.SHOW_ARGUMENTS); } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommandTest.java new file mode 100644 index 00000000..f41eeb20 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/UnregisterAdminCommandTest.java @@ -0,0 +1,71 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.output.MessageKey; +import org.bukkit.command.CommandSender; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.hamcrest.Matchers.equalToIgnoringCase; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Test for {@link UnregisterAdminCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class UnregisterAdminCommandTest { + + @InjectMocks + private UnregisterAdminCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private CommandService commandService; + + @Test + public void shouldHandleUnknownPlayer() { + // given + String user = "bobby"; + given(dataSource.isAuthAvailable(user)).willReturn(false); + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Collections.singletonList(user)); + + // then + verify(dataSource).isAuthAvailable(user); + verifyNoMoreInteractions(dataSource); + verify(commandService).send(sender, MessageKey.UNKNOWN_USER); + } + + @Test + public void shouldHandleDatabaseError() { + // given + String user = "personaNonGrata"; + given(dataSource.isAuthAvailable(argThat(equalToIgnoringCase(user)))).willReturn(true); + given(dataSource.removeAuth(argThat(equalToIgnoringCase(user)))).willReturn(false); + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Collections.singletonList(user)); + + // then + verify(dataSource).isAuthAvailable(argThat(equalToIgnoringCase(user))); + verify(dataSource).removeAuth(argThat(equalToIgnoringCase(user))); + verifyNoMoreInteractions(dataSource); + verify(commandService).send(sender, MessageKey.ERROR); + } + +} 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 new file mode 100644 index 00000000..5de45e61 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java @@ -0,0 +1,121 @@ +package fr.xephi.authme.command.executable.captcha; + +import fr.xephi.authme.cache.CaptchaManager; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.output.MessageKey; +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.runners.MockitoJUnitRunner; + +import java.util.Collections; + +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 CaptchaCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class CaptchaCommandTest { + + @InjectMocks + private CaptchaCommand command; + + @Mock + private CaptchaManager captchaManager; + + @Mock + private PlayerCache playerCache; + + @Mock + private CommandService commandService; + + @Test + public void shouldDetectIfPlayerIsLoggedIn() { + // given + String name = "creeper011"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList("123")); + + // then + verify(commandService).send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); + } + + @Test + public void shouldShowLoginUsageIfCaptchaIsNotRequired() { + // given + String name = "bobby"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(captchaManager.isCaptchaRequired(name)).willReturn(false); + + // when + command.executeCommand(player, Collections.singletonList("1234")); + + // then + verify(commandService).send(player, MessageKey.USAGE_LOGIN); + verify(captchaManager).isCaptchaRequired(name); + verifyNoMoreInteractions(captchaManager); + } + + @Test + public void shouldHandleCorrectCaptchaInput() { + // given + String name = "smith"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(captchaManager.isCaptchaRequired(name)).willReturn(true); + String captchaCode = "3991"; + given(captchaManager.checkCode(name, captchaCode)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList(captchaCode)); + + // then + verify(captchaManager).isCaptchaRequired(name); + verify(captchaManager).checkCode(name, captchaCode); + verifyNoMoreInteractions(captchaManager); + verify(commandService).send(player, MessageKey.CAPTCHA_SUCCESS); + verify(commandService).send(player, MessageKey.LOGIN_MESSAGE); + verifyNoMoreInteractions(commandService); + } + + @Test + public void shouldHandleWrongCaptchaInput() { + // given + String name = "smith"; + Player player = mockPlayerWithName(name); + given(playerCache.isAuthenticated(name)).willReturn(false); + given(captchaManager.isCaptchaRequired(name)).willReturn(true); + String captchaCode = "2468"; + given(captchaManager.checkCode(name, captchaCode)).willReturn(false); + String newCode = "1337"; + given(captchaManager.generateCode(name)).willReturn(newCode); + + // when + command.executeCommand(player, Collections.singletonList(captchaCode)); + + // then + verify(captchaManager).isCaptchaRequired(name); + verify(captchaManager).checkCode(name, captchaCode); + verify(captchaManager).generateCode(name); + verifyNoMoreInteractions(captchaManager); + verify(commandService).send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); + verifyNoMoreInteractions(commandService); + } + + private static Player mockPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java index bc8e0876..3a67bd77 100644 --- a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java @@ -1,26 +1,28 @@ package fr.xephi.authme.command.executable.changepassword; -import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.task.ChangePasswordTask; +import fr.xephi.authme.util.ValidationService; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.any; @@ -33,29 +35,40 @@ import static org.mockito.Mockito.when; /** * Test for {@link ChangePasswordCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class ChangePasswordCommandTest { + @InjectMocks + private ChangePasswordCommand command; + + @Mock private CommandService commandService; - @Before - public void setUpMocks() { - commandService = mock(CommandService.class); + @Mock + private PlayerCache playerCache; + @Mock + private ValidationService validationService; + + @Mock + private Management management; + + @Before + public void setSettings() { when(commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)).thenReturn(2); when(commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)).thenReturn(50); // Only allow passwords with alphanumerical characters for the test when(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)).thenReturn("[a-zA-Z0-9]+"); - when(commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS)).thenReturn(Collections. emptyList()); + when(commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS)).thenReturn(Collections.emptyList()); } @Test public void shouldRejectNonPlayerSender() { // given CommandSender sender = mock(BlockCommandSender.class); - ChangePasswordCommand command = new ChangePasswordCommand(); // when - command.executeCommand(sender, new ArrayList(), commandService); + command.executeCommand(sender, new ArrayList()); // then verify(sender).sendMessage(argThat(containsString("only for players"))); @@ -65,10 +78,9 @@ public class ChangePasswordCommandTest { public void shouldRejectNotLoggedInPlayer() { // given CommandSender sender = initPlayerWithName("name", false); - ChangePasswordCommand command = new ChangePasswordCommand(); // when - command.executeCommand(sender, Arrays.asList("pass", "pass"), commandService); + command.executeCommand(sender, Arrays.asList("pass", "pass")); // then verify(commandService).send(sender, MessageKey.NOT_LOGGED_IN); @@ -78,43 +90,39 @@ public class ChangePasswordCommandTest { public void shouldRejectInvalidPassword() { // given CommandSender sender = initPlayerWithName("abc12", true); - ChangePasswordCommand command = new ChangePasswordCommand(); String password = "newPW"; - given(commandService.validatePassword(password, "abc12")).willReturn(MessageKey.INVALID_PASSWORD_LENGTH); + given(validationService.validatePassword(password, "abc12")) + .willReturn(new ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH)); // when - command.executeCommand(sender, Arrays.asList("tester", password), commandService); + command.executeCommand(sender, Arrays.asList("tester", password)); // then - verify(commandService).validatePassword(password, "abc12"); - verify(commandService).send(sender, MessageKey.INVALID_PASSWORD_LENGTH); + verify(validationService).validatePassword(password, "abc12"); + verify(commandService).send(sender, MessageKey.INVALID_PASSWORD_LENGTH, new String[0]); } @Test public void shouldForwardTheDataForValidPassword() { // given - CommandSender sender = initPlayerWithName("parker", true); - ChangePasswordCommand command = new ChangePasswordCommand(); + String oldPass = "oldpass"; + String newPass = "abc123"; + Player player = initPlayerWithName("parker", true); + given(validationService.validatePassword("abc123", "parker")).willReturn(new ValidationResult()); // when - command.executeCommand(sender, Arrays.asList("abc123", "abc123"), commandService); + command.executeCommand(player, Arrays.asList(oldPass, newPass)); // then - verify(commandService).validatePassword("abc123", "parker"); - verify(commandService, never()).send(eq(sender), any(MessageKey.class)); - ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(ChangePasswordTask.class); - verify(commandService).runTaskAsynchronously(taskCaptor.capture()); - ChangePasswordTask task = taskCaptor.getValue(); - assertThat((String) ReflectionTestUtils.getFieldValue(ChangePasswordTask.class, task, "newPassword"), - equalTo("abc123")); + verify(validationService).validatePassword(newPass, "parker"); + verify(commandService, never()).send(eq(player), any(MessageKey.class)); + verify(management).performPasswordChange(player, oldPass, newPass); } private Player initPlayerWithName(String name, boolean loggedIn) { Player player = mock(Player.class); when(player.getName()).thenReturn(name); - PlayerCache playerCache = mock(PlayerCache.class); when(playerCache.isAuthenticated(name)).thenReturn(loggedIn); - when(commandService.getPlayerCache()).thenReturn(playerCache); return player; } diff --git a/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java index 1ea41355..143ac5fe 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java @@ -8,6 +8,7 @@ import org.bukkit.command.CommandSender; 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.runners.MockitoJUnitRunner; @@ -16,8 +17,8 @@ import java.util.Arrays; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link AddEmailCommand}. @@ -25,20 +26,25 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class AddEmailCommandTest { + @InjectMocks + private AddEmailCommand command; + @Mock private CommandService commandService; + @Mock + private Management management; + @Test public void shouldRejectNonPlayerSender() { // given CommandSender sender = mock(BlockCommandSender.class); - AddEmailCommand command = new AddEmailCommand(); // when - command.executeCommand(sender, new ArrayList(), commandService); + command.executeCommand(sender, new ArrayList()); // then - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); } @Test @@ -47,12 +53,9 @@ public class AddEmailCommandTest { Player sender = mock(Player.class); String email = "mail@example"; given(commandService.validateEmail(email)).willReturn(true); - Management management = mock(Management.class); - given(commandService.getManagement()).willReturn(management); - AddEmailCommand command = new AddEmailCommand(); // when - command.executeCommand(sender, Arrays.asList(email, email), commandService); + command.executeCommand(sender, Arrays.asList(email, email)); // then verify(management).performAddEmail(sender, email); @@ -64,13 +67,12 @@ public class AddEmailCommandTest { Player sender = mock(Player.class); String email = "asdfasdf@example.com"; given(commandService.validateEmail(email)).willReturn(true); - AddEmailCommand command = new AddEmailCommand(); // when - command.executeCommand(sender, Arrays.asList(email, "wrongConf"), commandService); + command.executeCommand(sender, Arrays.asList(email, "wrongConf")); // then - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); verify(commandService).send(sender, MessageKey.CONFIRM_EMAIL_MESSAGE); } diff --git a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java index 7e6d2fb9..240ace31 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java @@ -1,56 +1,54 @@ package fr.xephi.authme.command.executable.email; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.process.Management; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link ChangeEmailCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class ChangeEmailCommandTest { - private CommandService commandService; + @InjectMocks + private ChangeEmailCommand command; + + @Mock + private Management management; - @Before - public void setUpMocks() { - commandService = mock(CommandService.class); - } @Test public void shouldRejectNonPlayerSender() { // given CommandSender sender = mock(BlockCommandSender.class); - ChangeEmailCommand command = new ChangeEmailCommand(); // when - command.executeCommand(sender, new ArrayList(), commandService); + command.executeCommand(sender, new ArrayList()); // then - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); } @Test public void shouldForwardData() { // given Player sender = mock(Player.class); - ChangeEmailCommand command = new ChangeEmailCommand(); - Management management = mock(Management.class); - given(commandService.getManagement()).willReturn(management); // when - command.executeCommand(sender, Arrays.asList("new.mail@example.org", "old_mail@example.org"), commandService); + command.executeCommand(sender, Arrays.asList("new.mail@example.org", "old_mail@example.org")); // then verify(management).performChangeEmail(sender, "new.mail@example.org", "old_mail@example.org"); diff --git a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java index 9a23bc17..21e938bf 100644 --- a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.command.executable.login; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.process.Management; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; 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.runners.MockitoJUnitRunner; @@ -14,12 +14,11 @@ import java.util.ArrayList; import java.util.Collections; import static org.hamcrest.Matchers.containsString; -import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link LoginCommand}. @@ -27,20 +26,23 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class LoginCommandTest { + @InjectMocks + private LoginCommand command; + @Mock - private CommandService commandService; + private Management management; + @Test public void shouldStopIfSenderIsNotAPlayer() { // given CommandSender sender = mock(BlockCommandSender.class); - LoginCommand command = new LoginCommand(); // when - command.executeCommand(sender, new ArrayList(), commandService); + command.executeCommand(sender, new ArrayList()); // then - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); verify(sender).sendMessage(argThat(containsString("only for players"))); } @@ -48,12 +50,9 @@ public class LoginCommandTest { public void shouldCallManagementForPlayerCaller() { // given Player sender = mock(Player.class); - LoginCommand command = new LoginCommand(); - Management management = mock(Management.class); - given(commandService.getManagement()).willReturn(management); // when - command.executeCommand(sender, Collections.singletonList("password"), commandService); + command.executeCommand(sender, Collections.singletonList("password")); // then verify(management).performLogin(eq(sender), eq("password"), eq(false)); diff --git a/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java index 7ac3ede6..7bc9eeb1 100644 --- a/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/logout/LogoutCommandTest.java @@ -1,46 +1,47 @@ package fr.xephi.authme.command.executable.logout; -import fr.xephi.authme.command.CommandService; import fr.xephi.authme.process.Management; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Collections; import static org.hamcrest.Matchers.containsString; -import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link LogoutCommand}. */ +@RunWith(MockitoJUnitRunner.class) public class LogoutCommandTest { - private CommandService commandService; + @InjectMocks + private LogoutCommand command; + + @Mock + private Management management; - @Before - public void initializeAuthMeMock() { - commandService = mock(CommandService.class); - } @Test public void shouldStopIfSenderIsNotAPlayer() { // given CommandSender sender = mock(BlockCommandSender.class); - LogoutCommand command = new LogoutCommand(); // when - command.executeCommand(sender, new ArrayList(), commandService); + command.executeCommand(sender, new ArrayList()); // then - verify(commandService, never()).getManagement(); + verifyZeroInteractions(management); verify(sender).sendMessage(argThat(containsString("only for players"))); } @@ -48,12 +49,9 @@ public class LogoutCommandTest { public void shouldCallManagementForPlayerCaller() { // given Player sender = mock(Player.class); - LogoutCommand command = new LogoutCommand(); - Management management = mock(Management.class); - given(commandService.getManagement()).willReturn(management); // when - command.executeCommand(sender, Collections.singletonList("password"), commandService); + command.executeCommand(sender, Collections.singletonList("password")); // then verify(management).performLogout(sender); 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 6f86a5f6..217b74be 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 @@ -2,7 +2,6 @@ package fr.xephi.authme.command.executable.register; import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.CommandService; -import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; @@ -19,6 +18,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -40,12 +40,15 @@ import static org.mockito.Mockito.verifyZeroInteractions; @RunWith(MockitoJUnitRunner.class) public class RegisterCommandTest { + @InjectMocks + private RegisterCommand command; + @Mock private CommandService commandService; + @Mock private Management management; - @Mock - private Player sender; + @BeforeClass public static void setup() { @@ -54,7 +57,6 @@ public class RegisterCommandTest { @Before public void linkMocksAndProvideSettingDefaults() { - given(commandService.getManagement()).willReturn(management); given(commandService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.BCRYPT); given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(false); given(commandService.getProperty(RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION)).willReturn(false); @@ -64,10 +66,9 @@ public class RegisterCommandTest { public void shouldNotRunForNonPlayerSender() { // given CommandSender sender = mock(BlockCommandSender.class); - RegisterCommand command = new RegisterCommand(); // when - command.executeCommand(sender, new ArrayList(), commandService); + command.executeCommand(sender, new ArrayList()); // then verify(sender).sendMessage(argThat(containsString("Player only!"))); @@ -78,25 +79,25 @@ public class RegisterCommandTest { public void shouldForwardToManagementForTwoFactor() { // given given(commandService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.TWO_FACTOR); - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Collections.emptyList(), commandService); + command.executeCommand(player, Collections.emptyList()); // then - verify(management).performRegister(sender, "", ""); + verify(management).performRegister(player, "", "", true); } @Test public void shouldReturnErrorForEmptyArguments() { // given - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Collections.emptyList(), commandService); + command.executeCommand(player, Collections.emptyList()); // then - verify(commandService).send(sender, MessageKey.USAGE_REGISTER); + verify(commandService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management); } @@ -104,13 +105,13 @@ public class RegisterCommandTest { public void shouldReturnErrorForMissingConfirmation() { // given given(commandService.getProperty(RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION)).willReturn(true); - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Collections.singletonList("arrrr"), commandService); + command.executeCommand(player, Collections.singletonList("arrrr")); // then - verify(commandService).send(sender, MessageKey.USAGE_REGISTER); + verify(commandService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management); } @@ -119,13 +120,13 @@ public class RegisterCommandTest { // given given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Collections.singletonList("test@example.org"), commandService); + command.executeCommand(player, Collections.singletonList("test@example.org")); // then - verify(commandService).send(sender, MessageKey.USAGE_REGISTER); + verify(commandService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management); } @@ -135,13 +136,13 @@ public class RegisterCommandTest { given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(false); given(commandService.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(""); - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Collections.singletonList("myMail@example.tld"), commandService); + command.executeCommand(player, Collections.singletonList("myMail@example.tld")); // then - verify(sender).sendMessage(argThat(containsString("no email address"))); + verify(player).sendMessage(argThat(containsString("no email address"))); verifyZeroInteractions(management); } @@ -154,15 +155,14 @@ public class RegisterCommandTest { given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); given(commandService.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("server@example.com"); - - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Arrays.asList(playerMail, playerMail), commandService); + command.executeCommand(player, Arrays.asList(playerMail, playerMail)); // then verify(commandService).validateEmail(playerMail); - verify(commandService).send(sender, MessageKey.INVALID_EMAIL); + verify(commandService).send(player, MessageKey.INVALID_EMAIL); verifyZeroInteractions(management); } @@ -175,14 +175,13 @@ public class RegisterCommandTest { given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); given(commandService.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("server@example.com"); - - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Arrays.asList(playerMail, "invalid"), commandService); + command.executeCommand(player, Arrays.asList(playerMail, "invalid")); // then - verify(commandService).send(sender, MessageKey.USAGE_REGISTER); + verify(commandService).send(player, MessageKey.USAGE_REGISTER); verifyZeroInteractions(management); } @@ -197,40 +196,40 @@ public class RegisterCommandTest { given(commandService.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); given(commandService.getProperty(RegistrationSettings.ENABLE_CONFIRM_EMAIL)).willReturn(true); given(commandService.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("server@example.com"); - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Arrays.asList(playerMail, playerMail), commandService); + command.executeCommand(player, Arrays.asList(playerMail, playerMail)); // then verify(commandService).validateEmail(playerMail); - verify(management).performRegister(eq(sender), argThat(stringWithLength(passLength)), eq(playerMail)); + verify(management).performRegister(eq(player), argThat(stringWithLength(passLength)), eq(playerMail), eq(true)); } @Test public void shouldRejectInvalidPasswordConfirmation() { // given given(commandService.getProperty(RestrictionSettings.ENABLE_PASSWORD_CONFIRMATION)).willReturn(true); - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Arrays.asList("myPass", "mypass"), commandService); + command.executeCommand(player, Arrays.asList("myPass", "mypass")); // then - verify(commandService).send(sender, MessageKey.PASSWORD_MATCH_ERROR); + verify(commandService).send(player, MessageKey.PASSWORD_MATCH_ERROR); verifyZeroInteractions(management); } @Test public void shouldPerformPasswordValidation() { // given - ExecutableCommand command = new RegisterCommand(); + Player player = mock(Player.class); // when - command.executeCommand(sender, Collections.singletonList("myPass"), commandService); + command.executeCommand(player, Collections.singletonList("myPass")); // then - verify(management).performRegister(sender, "myPass", ""); + verify(management).performRegister(player, "myPass", "", true); } diff --git a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java new file mode 100644 index 00000000..976649ae --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java @@ -0,0 +1,74 @@ +package fr.xephi.authme.command.executable.unregister; + +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.process.Management; +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.runners.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link UnregisterCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class UnregisterCommandTest { + + @InjectMocks + private UnregisterCommand command; + + @Mock + private Management management; + + @Mock + private CommandService commandService; + + @Mock + private PlayerCache playerCache; + + @Test + public void shouldCatchUnauthenticatedUser() { + // given + String password = "mySecret123"; + String name = "player77"; + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + given(playerCache.isAuthenticated(name)).willReturn(false); + + // when + command.executeCommand(player, Collections.singletonList(password)); + + // then + verify(playerCache).isAuthenticated(name); + verify(commandService).send(player, MessageKey.NOT_LOGGED_IN); + verifyZeroInteractions(management); + } + + @Test + public void shouldForwardDataToAsyncTask() { + // given + String password = "p@ssw0rD"; + String name = "jas0n_"; + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + given(playerCache.isAuthenticated(name)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList(password)); + + // then + verify(playerCache).isAuthenticated(name); + verify(management).performUnregister(player, password, false); + } + +} diff --git a/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java b/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java index 608951de..77320d15 100644 --- a/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java +++ b/src/test/java/fr/xephi/authme/command/help/CommandSyntaxHelperTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.help; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.TestCommandsUtil; import org.bukkit.ChatColor; @@ -66,4 +67,10 @@ public class CommandSyntaxHelperTest { assertThat(result, equalTo(ChatColor.WHITE + "/email" + ChatColor.YELLOW + " [player]")); } + @Test + public void shouldHaveHiddenConstructor() { + // given / when / then + TestHelper.validateHasOnlyPrivateEmptyConstructor(CommandSyntaxHelper.class); + } + } diff --git a/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java b/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java index 3ec47a4f..b5450fb0 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpProviderTest.java @@ -4,13 +4,16 @@ import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.FoundResultStatus; import fr.xephi.authme.command.TestCommandsUtil; +import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.permission.PlayerPermission; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.PluginSettings; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.ArgumentCaptor; import java.util.Arrays; import java.util.Collections; @@ -31,7 +34,9 @@ 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.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Test for {@link HelpProvider}. @@ -52,7 +57,9 @@ public class HelpProviderTest { @Before public void setUpHelpProvider() { permissionsManager = mock(PermissionsManager.class); - helpProvider = new HelpProvider(permissionsManager, HELP_HEADER); + NewSetting settings = mock(NewSetting.class); + given(settings.getProperty(PluginSettings.HELP_HEADER)).willReturn(HELP_HEADER); + helpProvider = new HelpProvider(permissionsManager, settings); sender = mock(CommandSender.class); } @@ -63,9 +70,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Arrays.asList("authme", "login")); // when - List lines = helpProvider.printHelp(sender, result, SHOW_LONG_DESCRIPTION); + helpProvider.outputHelp(sender, result, SHOW_LONG_DESCRIPTION); // then + List lines = getLines(sender); assertThat(lines, hasSize(5)); assertThat(lines.get(0), containsString(HELP_HEADER + " HELP")); assertThat(removeColors(lines.get(1)), containsString("Command: /authme login ")); @@ -81,9 +89,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Arrays.asList("authme", "reg")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_ARGUMENTS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_ARGUMENTS); // then + List lines = getLines(sender); assertThat(lines, hasSize(4)); assertThat(lines.get(0), containsString(HELP_HEADER + " HELP")); assertThat(removeColors(lines.get(1)), equalTo("Arguments:")); @@ -98,9 +107,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Collections.singletonList("email")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_ARGUMENTS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_ARGUMENTS); // then + List lines = getLines(sender); assertThat(lines, hasSize(3)); assertThat(removeColors(lines.get(2)), containsString("player: 'player' argument description (Optional)")); } @@ -113,29 +123,31 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_ARGUMENTS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_ARGUMENTS); // then + List lines = getLines(sender); assertThat(lines, hasSize(1)); // only has the help banner } @Test public void shouldShowAndEvaluatePermissions() { // given - CommandDescription command = getCommandWithLabel(commands, "authme", "login"); - FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme")); + CommandDescription command = getCommandWithLabel(commands, "unregister"); + FoundCommandResult result = newFoundResult(command, Collections.singletonList("unreg")); given(sender.isOp()).willReturn(true); - given(permissionsManager.hasPermission(sender, PlayerPermission.LOGIN)).willReturn(true); - given(permissionsManager.hasPermission(sender, command)).willReturn(true); + given(permissionsManager.hasPermission(sender, AdminPermission.UNREGISTER)).willReturn(true); + given(permissionsManager.hasPermission(sender, command.getPermission())).willReturn(true); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); // then + List lines = getLines(sender); assertThat(lines, hasSize(5)); assertThat(removeColors(lines.get(1)), containsString("Permissions:")); assertThat(removeColors(lines.get(2)), - containsString(PlayerPermission.LOGIN.getNode() + " (You have permission)")); + containsString(AdminPermission.UNREGISTER.getNode() + " (You have permission)")); assertThat(removeColors(lines.get(3)), containsString("Default: OP's only (You have permission)")); assertThat(removeColors(lines.get(4)), containsString("Result: You have permission")); } @@ -143,20 +155,21 @@ public class HelpProviderTest { @Test public void shouldShowAndEvaluateForbiddenPermissions() { // given - CommandDescription command = getCommandWithLabel(commands, "authme", "login"); - FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme")); + CommandDescription command = getCommandWithLabel(commands, "unregister"); + FoundCommandResult result = newFoundResult(command, Collections.singletonList("unregister")); given(sender.isOp()).willReturn(false); - given(permissionsManager.hasPermission(sender, PlayerPermission.LOGIN)).willReturn(false); - given(permissionsManager.hasPermission(sender, command)).willReturn(false); + given(permissionsManager.hasPermission(sender, AdminPermission.UNREGISTER)).willReturn(false); + given(permissionsManager.hasPermission(sender, command.getPermission())).willReturn(false); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); // then + List lines = getLines(sender); assertThat(lines, hasSize(5)); assertThat(removeColors(lines.get(1)), containsString("Permissions:")); assertThat(removeColors(lines.get(2)), - containsString(PlayerPermission.LOGIN.getNode() + " (No permission)")); + containsString(AdminPermission.UNREGISTER.getNode() + " (No permission)")); assertThat(removeColors(lines.get(3)), containsString("Default: OP's only (No permission)")); assertThat(removeColors(lines.get(4)), containsString("Result: No permission")); } @@ -168,9 +181,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); // then + List lines = getLines(sender); assertThat(lines, hasSize(1)); } @@ -178,14 +192,15 @@ public class HelpProviderTest { public void shouldNotShowAnythingForNullPermissionsOnCommand() { // given CommandDescription command = mock(CommandDescription.class); - given(command.getCommandPermissions()).willReturn(null); + given(command.getPermission()).willReturn(null); given(command.getLabels()).willReturn(Collections.singletonList("test")); FoundCommandResult result = newFoundResult(command, Collections.singletonList("test")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_PERMISSIONS); // then + List lines = getLines(sender); assertThat(lines, hasSize(1)); } @@ -196,9 +211,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Arrays.asList("authme", "reg")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_ALTERNATIVES); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_ALTERNATIVES); // then + List lines = getLines(sender); assertThat(lines, hasSize(4)); assertThat(removeColors(lines.get(1)), containsString("Alternatives:")); assertThat(removeColors(lines.get(2)), containsString("/authme register ")); @@ -212,9 +228,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Arrays.asList("authme", "login")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_ALTERNATIVES); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_ALTERNATIVES); // then + List lines = getLines(sender); assertThat(lines, hasSize(1)); } @@ -225,9 +242,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_CHILDREN); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_CHILDREN); // then + List lines = getLines(sender); assertThat(lines, hasSize(4)); assertThat(removeColors(lines.get(1)), containsString("Commands:")); assertThat(removeColors(lines.get(2)), containsString("/authme login: login cmd")); @@ -241,9 +259,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Collections.singletonList("authme")); // when - List lines = helpProvider.printHelp(sender, result, HIDE_COMMAND | SHOW_CHILDREN); + helpProvider.outputHelp(sender, result, HIDE_COMMAND | SHOW_CHILDREN); // then + List lines = getLines(sender); assertThat(lines, hasSize(1)); } @@ -251,12 +270,13 @@ public class HelpProviderTest { public void shouldHandleUnboundFoundCommandResult() { // given FoundCommandResult result = new FoundCommandResult(null, Arrays.asList("authme", "test"), - Collections. emptyList(), 0.0, FoundResultStatus.UNKNOWN_LABEL); + Collections.emptyList(), 0.0, FoundResultStatus.UNKNOWN_LABEL); // when - List lines = helpProvider.printHelp(sender, result, ALL_OPTIONS); + helpProvider.outputHelp(sender, result, ALL_OPTIONS); // then + List lines = getLines(sender); assertThat(lines, hasSize(1)); assertThat(lines.get(0), containsString("Failed to retrieve any help information")); } @@ -272,9 +292,10 @@ public class HelpProviderTest { FoundCommandResult result = newFoundResult(command, Arrays.asList("authme", "ragister")); // when - List lines = helpProvider.printHelp(sender, result, 0); + helpProvider.outputHelp(sender, result, 0); // then + List lines = getLines(sender); assertThat(lines, hasSize(2)); assertThat(lines.get(0), containsString(HELP_HEADER + " HELP")); assertThat(removeColors(lines.get(1)), containsString("Command: /authme register ")); @@ -315,7 +336,7 @@ public class HelpProviderTest { * @return The generated FoundCommandResult object */ private static FoundCommandResult newFoundResult(CommandDescription command, List labels) { - return new FoundCommandResult(command, labels, Collections. emptyList(), 0.0, FoundResultStatus.SUCCESS); + return new FoundCommandResult(command, labels, Collections.emptyList(), 0.0, FoundResultStatus.SUCCESS); } private static String removeColors(String str) { @@ -324,5 +345,11 @@ public class HelpProviderTest { } return str; } + + private static List getLines(CommandSender sender) { + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(sender, atLeastOnce()).sendMessage(captor.capture()); + return captor.getAllValues(); + } } diff --git a/src/test/java/fr/xephi/authme/converter/CrazyLoginConverterTest.java b/src/test/java/fr/xephi/authme/converter/CrazyLoginConverterTest.java new file mode 100644 index 00000000..e64fb97c --- /dev/null +++ b/src/test/java/fr/xephi/authme/converter/CrazyLoginConverterTest.java @@ -0,0 +1,95 @@ +package fr.xephi.authme.converter; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.ConverterSettings; +import org.bukkit.command.CommandSender; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.File; +import java.util.List; + +import static fr.xephi.authme.AuthMeMatchers.equalToHash; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link CrazyLoginConverter}. + */ +@RunWith(MockitoJUnitRunner.class) +public class CrazyLoginConverterTest { + + private CrazyLoginConverter crazyLoginConverter; + + @Mock + private DataSource dataSource; + + @Mock + private NewSetting settings; + + private File dataFolder = TestHelper.getJarFile("/converter/"); + + @BeforeClass + public static void initializeLogger() { + TestHelper.setupLogger(); + } + + @Before + public void instantiateConverter() { + crazyLoginConverter = new CrazyLoginConverter(dataFolder, dataSource, settings); + } + + @Test + public void shouldImportUsers() { + // given + given(settings.getProperty(ConverterSettings.CRAZYLOGIN_FILE_NAME)).willReturn("crazylogin.db"); + CommandSender sender = mock(CommandSender.class); + + // when + crazyLoginConverter.execute(sender); + + // then + ArgumentCaptor authCaptor = ArgumentCaptor.forClass(PlayerAuth.class); + verify(dataSource, times(2)).saveAuth(authCaptor.capture()); + List savedAuths = authCaptor.getAllValues(); + assertNameAndRealName(savedAuths.get(0), "qotato", "qotaTo"); + assertThat(savedAuths.get(0).getPassword(), equalToHash("8267663ab198a96437b9f455429a2c1b6c943111613c217bf2703c14d08a309d34e510ddb5549507b1500759dbcf9d4a99bc765ff37b32bd31adbb1e92e74ac5")); + assertNameAndRealName(savedAuths.get(1), "bobby", "Bobby"); + assertThat(savedAuths.get(1).getPassword(), equalToHash("ad50dbc841e6321210530801f5219a5ffb4c7c41f11878d465374a4b8db2965c50f69b6098918a58e4adea312e3633c7724b15e24a217009e6fa2b3c299d55f2")); + } + + @Test + public void shouldStopForNonExistentFile() { + // given + given(settings.getProperty(ConverterSettings.CRAZYLOGIN_FILE_NAME)).willReturn("invalid-file"); + CommandSender sender = mock(CommandSender.class); + + // when + crazyLoginConverter.execute(sender); + + // then + verifyZeroInteractions(dataSource); + verify(sender).sendMessage(argThat(containsString("file not found"))); + } + + private static void assertNameAndRealName(PlayerAuth auth, String name, String realName) { + assertThat(auth.getNickname(), equalTo(name)); + assertThat(auth.getRealName(), equalTo(realName)); + } + +} diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java index 30f3484e..bf8d54d5 100644 --- a/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java @@ -10,6 +10,8 @@ import java.util.List; import static fr.xephi.authme.AuthMeMatchers.equalToHash; import static fr.xephi.authme.AuthMeMatchers.hasAuthBasicData; import static fr.xephi.authme.AuthMeMatchers.hasAuthLocation; +import java.util.HashSet; +import java.util.Set; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; @@ -221,7 +223,7 @@ public abstract class AbstractDataSourceIntegrationTest { public void shouldDeletePlayers() { // given DataSource dataSource = getDataSource(); - List playersToDelete = Arrays.asList("bobby", "doesNotExist"); + Set playersToDelete = new HashSet<>(Arrays.asList("bobby", "doesNotExist")); assumeThat(dataSource.getAccountsRegistered(), equalTo(2)); // when diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java index 75908e3e..1ecdb637 100644 --- a/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java @@ -32,6 +32,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -192,8 +193,8 @@ public abstract class AbstractResourceClosingTest { for (Class paramType : method.getParameterTypes()) { // Checking List.class == paramType instead of Class#isAssignableFrom means we really only accept List, // but that is a sensible assumption and makes our life much easier later on when juggling with Type - Object param = (List.class == paramType) - ? getTypedList(method.getGenericParameterTypes()[index]) + Object param = Collection.class.isAssignableFrom(paramType) + ? getTypedCollection(method.getGenericParameterTypes()[index]) : PARAM_VALUES.get(paramType); Preconditions.checkNotNull(param, "No param type for " + paramType); params.add(param); @@ -203,20 +204,27 @@ public abstract class AbstractResourceClosingTest { } /** - * Return a list with some test elements that correspond to the given list type's generic type. + * Return a collection of the required type with some test elements that correspond to the + * collection's generic type. * - * @param type The list type to process and build a test list for - * @return Test list with sample elements of the correct type + * @param type The collection type to process and build a test collection for + * @return Test collection with sample elements of the correct type */ - private static List getTypedList(Type type) { + private static Collection getTypedCollection(Type type) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; - Preconditions.checkArgument(List.class == parameterizedType.getRawType(), type + " should be a List"); + Preconditions.checkArgument(Collection.class.isAssignableFrom((Class) parameterizedType.getRawType()), + type + " should extend from Collection"); Type genericType = parameterizedType.getActualTypeArguments()[0]; Object element = PARAM_VALUES.get(genericType); Preconditions.checkNotNull(element, "No sample element for list of generic type " + genericType); - return Arrays.asList(element, element, element); + if (List.class == parameterizedType.getRawType()) { + return Arrays.asList(element, element, element); + } else if (Set.class == parameterizedType.getRawType()) { + return new HashSet<>(Arrays.asList(element, element, element)); + } + throw new IllegalStateException("Unknown collection type " + parameterizedType.getRawType()); } throw new IllegalStateException("Cannot build list for unexpected Type: " + type); } diff --git a/src/test/java/fr/xephi/authme/events/EventsConsistencyTest.java b/src/test/java/fr/xephi/authme/events/EventsConsistencyTest.java index d7ba5f74..0f8340d8 100644 --- a/src/test/java/fr/xephi/authme/events/EventsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/events/EventsConsistencyTest.java @@ -75,6 +75,7 @@ public class EventsConsistencyTest { return !clazz.isInterface() && !clazz.isEnum() && !Modifier.isAbstract(clazz.getModifiers()); } + @SuppressWarnings("unchecked") private static Class getEventClassFromFile(File file) { String fileName = file.getPath(); String className = fileName diff --git a/src/test/java/fr/xephi/authme/hooks/PluginHooksTest.java b/src/test/java/fr/xephi/authme/hooks/PluginHooksTest.java index e8475d9c..eef56144 100644 --- a/src/test/java/fr/xephi/authme/hooks/PluginHooksTest.java +++ b/src/test/java/fr/xephi/authme/hooks/PluginHooksTest.java @@ -2,8 +2,13 @@ package fr.xephi.authme.hooks; import com.earth2me.essentials.Essentials; import com.earth2me.essentials.User; +import com.onarandombox.MultiverseCore.MultiverseCore; +import com.onarandombox.MultiverseCore.api.MVWorldManager; +import com.onarandombox.MultiverseCore.api.MultiverseWorld; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; @@ -20,6 +25,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; /** @@ -27,6 +33,11 @@ import static org.mockito.Mockito.verify; */ public class PluginHooksTest { + /** The plugin name of Essentials. */ + private static final String ESSENTIALS = "Essentials"; + /** The plugin name of Multiverse-Core. */ + private static final String MULTIVERSE = "Multiverse-Core"; + @BeforeClass public static void setLogger() { TestHelper.setupLogger(); @@ -37,7 +48,7 @@ public class PluginHooksTest { // given PluginManager pluginManager = mock(PluginManager.class); PluginHooks pluginHooks = new PluginHooks(pluginManager); - setPluginAvailable(pluginManager, "Essentials", Essentials.class); + setPluginAvailable(pluginManager, ESSENTIALS, Essentials.class); assertThat(pluginHooks.isEssentialsAvailable(), equalTo(false)); // when @@ -53,7 +64,7 @@ public class PluginHooksTest { public void shouldHookIntoEssentialsAtInitialization() { // given PluginManager pluginManager = mock(PluginManager.class); - setPluginAvailable(pluginManager, "Essentials", Essentials.class); + setPluginAvailable(pluginManager, ESSENTIALS, Essentials.class); // when PluginHooks pluginHooks = new PluginHooks(pluginManager); @@ -62,6 +73,19 @@ public class PluginHooksTest { assertThat(pluginHooks.isEssentialsAvailable(), equalTo(true)); } + @Test + public void shouldHookIntoMultiverseAtInitialization() { + // given + PluginManager pluginManager = mock(PluginManager.class); + setPluginAvailable(pluginManager, MULTIVERSE, MultiverseCore.class); + + // when + PluginHooks pluginHooks = new PluginHooks(pluginManager); + + // then + assertThat(pluginHooks.isMultiverseAvailable(), equalTo(true)); + } + @Test public void shouldReturnEssentialsDataFolder() { // given @@ -71,7 +95,7 @@ public class PluginHooksTest { ReflectionTestUtils.setField(JavaPlugin.class, ess, "dataFolder", essDataFolder); PluginManager pluginManager = mock(PluginManager.class); - setPluginAvailable(pluginManager, "Essentials", ess); + setPluginAvailable(pluginManager, ESSENTIALS, ess); PluginHooks pluginHooks = new PluginHooks(pluginManager); // when @@ -104,7 +128,7 @@ public class PluginHooksTest { given(ess.getUser(player)).willReturn(user); PluginManager pluginManager = mock(PluginManager.class); - setPluginAvailable(pluginManager, "Essentials", ess); + setPluginAvailable(pluginManager, ESSENTIALS, ess); PluginHooks pluginHooks = new PluginHooks(pluginManager); // when @@ -125,17 +149,20 @@ public class PluginHooksTest { } @Test - public void shouldUnhookEssentials() { + public void shouldUnhookEssentialsAndMultiverse() { // given PluginManager pluginManager = mock(PluginManager.class); - setPluginAvailable(pluginManager, "Essentials", Essentials.class); + setPluginAvailable(pluginManager, ESSENTIALS, Essentials.class); + setPluginAvailable(pluginManager, MULTIVERSE, MultiverseCore.class); PluginHooks pluginHooks = new PluginHooks(pluginManager); // when pluginHooks.unhookEssentials(); + pluginHooks.unhookMultiverse(); // then assertThat(pluginHooks.isEssentialsAvailable(), equalTo(false)); + assertThat(pluginHooks.isMultiverseAvailable(), equalTo(false)); } @Test @@ -154,6 +181,70 @@ public class PluginHooksTest { assertThat(pluginHooks.isCombatTagPlusAvailable(), equalTo(false)); } + @Test + public void shouldReturnNullForUnavailableMultiverse() { + // given + PluginManager pluginManager = mock(PluginManager.class); + PluginHooks pluginHooks = new PluginHooks(pluginManager); + World world = mock(World.class); + + // when + Location result = pluginHooks.getMultiverseSpawn(world); + + // then + assertThat(result, nullValue()); + } + + @Test + public void shouldGetMultiverseSpawn() { + // given + Location location = mock(Location.class); + MultiverseWorld multiverseWorld = mock(MultiverseWorld.class); + given(multiverseWorld.getSpawnLocation()).willReturn(location); + + World world = mock(World.class); + MVWorldManager mvWorldManager = mock(MVWorldManager.class); + given(mvWorldManager.isMVWorld(world)).willReturn(true); + given(mvWorldManager.getMVWorld(world)).willReturn(multiverseWorld); + MultiverseCore multiverse = mock(MultiverseCore.class); + given(multiverse.getMVWorldManager()).willReturn(mvWorldManager); + + PluginManager pluginManager = mock(PluginManager.class); + setPluginAvailable(pluginManager, MULTIVERSE, multiverse); + PluginHooks pluginHooks = new PluginHooks(pluginManager); + + // when + Location spawn = pluginHooks.getMultiverseSpawn(world); + + // then + assertThat(spawn, equalTo(location)); + verify(mvWorldManager).isMVWorld(world); + verify(mvWorldManager).getMVWorld(world); + verify(multiverseWorld).getSpawnLocation(); + } + + @Test + public void shouldReturnNullForNonMvWorld() { + // given + World world = mock(World.class); + MVWorldManager mvWorldManager = mock(MVWorldManager.class); + given(mvWorldManager.isMVWorld(world)).willReturn(false); + + PluginManager pluginManager = mock(PluginManager.class); + MultiverseCore multiverse = mock(MultiverseCore.class); + setPluginAvailable(pluginManager, MULTIVERSE, multiverse); + given(multiverse.getMVWorldManager()).willReturn(mvWorldManager); + PluginHooks pluginHooks = new PluginHooks(pluginManager); + + // when + Location spawn = pluginHooks.getMultiverseSpawn(world); + + // then + assertThat(spawn, nullValue()); + verify(mvWorldManager).isMVWorld(world); + verify(mvWorldManager, never()).getMVWorld(world); + } + private static void setPluginAvailable(PluginManager managerMock, String pluginName, Class pluginClass) { setPluginAvailable(managerMock, pluginName, mock(pluginClass)); diff --git a/src/test/java/fr/xephi/authme/initialization/AuthMeServiceInitializerTest.java b/src/test/java/fr/xephi/authme/initialization/AuthMeServiceInitializerTest.java new file mode 100644 index 00000000..e03a1435 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/AuthMeServiceInitializerTest.java @@ -0,0 +1,340 @@ +package fr.xephi.authme.initialization; + +import fr.xephi.authme.initialization.samples.AlphaService; +import fr.xephi.authme.initialization.samples.BadFieldInjection; +import fr.xephi.authme.initialization.samples.BetaManager; +import fr.xephi.authme.initialization.samples.CircularClasses; +import fr.xephi.authme.initialization.samples.ClassWithAbstractDependency; +import fr.xephi.authme.initialization.samples.ClassWithAnnotations; +import fr.xephi.authme.initialization.samples.Duration; +import fr.xephi.authme.initialization.samples.FieldInjectionWithAnnotations; +import fr.xephi.authme.initialization.samples.GammaService; +import fr.xephi.authme.initialization.samples.InstantiationFallbackClasses; +import fr.xephi.authme.initialization.samples.InvalidClass; +import fr.xephi.authme.initialization.samples.InvalidPostConstruct; +import fr.xephi.authme.initialization.samples.InvalidStaticFieldInjection; +import fr.xephi.authme.initialization.samples.PostConstructTestClass; +import fr.xephi.authme.initialization.samples.ProvidedClass; +import fr.xephi.authme.initialization.samples.Size; +import fr.xephi.authme.settings.NewSetting; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.hamcrest.Matchers.containsString; +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.Mockito.mock; + +/** + * Test for {@link AuthMeServiceInitializer}. + */ +public class AuthMeServiceInitializerTest { + + private static final String ALLOWED_PACKAGE = "fr.xephi.authme.initialization"; + + private AuthMeServiceInitializer initializer; + + // As we test many cases that throw exceptions, we use JUnit's ExpectedException Rule + // to make sure that we receive the exception we expect + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Before + public void setInitializer() { + initializer = new AuthMeServiceInitializer(ALLOWED_PACKAGE); + initializer.register(ProvidedClass.class, new ProvidedClass("")); + } + + @Test + public void shouldInitializeElements() { + // given / when + BetaManager betaManager = initializer.get(BetaManager.class); + + // then + assertThat(betaManager, not(nullValue())); + for (Object o : betaManager.getDependencies()) { + assertThat(o, not(nullValue())); + } + } + + @Test + public void shouldThrowForInvalidPackage() { + // given / when / then + expectRuntimeExceptionWith("outside of the allowed packages"); + initializer.get(InvalidClass.class); + } + + @Test + public void shouldThrowForUnregisteredPrimitiveType() { + // given / when / then + expectRuntimeExceptionWith("Primitive types must be provided"); + initializer.get(int.class); + } + + @Test + public void shouldPassValueByAnnotation() { + // given + int size = 12; + long duration = -15482L; + initializer.provide(Size.class, size); + initializer.provide(Duration.class, duration); + + // when + ClassWithAnnotations object = initializer.get(ClassWithAnnotations.class); + + // then + assertThat(object, not(nullValue())); + assertThat(object.getSize(), equalTo(size)); + assertThat(object.getDuration(), equalTo(duration)); + // some sample check to make sure we only have one instance of GammaService + assertThat(object.getGammaService(), equalTo(initializer.get(BetaManager.class).getDependencies()[1])); + } + + @Test + public void shouldRecognizeCircularReferences() { + // given / when / then + expectRuntimeExceptionWith("Found cyclic dependency"); + initializer.get(CircularClasses.Circular3.class); + } + + @Test + public void shouldThrowForUnregisteredAnnotation() { + // given + initializer.provide(Size.class, 4523); + + // when / then + expectRuntimeExceptionWith("must be registered beforehand"); + initializer.get(ClassWithAnnotations.class); + } + + @Test + public void shouldThrowForFieldInjectionWithoutNoArgsConstructor() { + // given / when / then + expectRuntimeExceptionWith("Did not find injection method"); + initializer.get(BadFieldInjection.class); + } + + @Test + public void shouldInjectFieldsWithAnnotationsProperly() { + // given + initializer.provide(Size.class, 2809375); + initializer.provide(Duration.class, 13095L); + + // when + FieldInjectionWithAnnotations result = initializer.get(FieldInjectionWithAnnotations.class); + + // then + assertThat(result.getSize(), equalTo(2809375)); + assertThat(result.getDuration(), equalTo(13095L)); + assertThat(result.getBetaManager(), not(nullValue())); + assertThat(result.getClassWithAnnotations(), not(nullValue())); + assertThat(result.getClassWithAnnotations().getGammaService(), + equalTo(result.getBetaManager().getDependencies()[1])); + } + + @Test + public void shouldThrowForAnnotationAsKey() { + // given / when / then + expectRuntimeExceptionWith("Cannot retrieve annotated elements in this way"); + initializer.get(Size.class); + } + + @Test + public void shouldThrowForSecondRegistration() { + // given / when / then + expectRuntimeExceptionWith("There is already an object present"); + initializer.register(ProvidedClass.class, new ProvidedClass("")); + } + + @Test + public void shouldThrowForSecondAnnotationRegistration() { + // given + initializer.provide(Size.class, 12); + + // when / then + expectRuntimeExceptionWith("already registered"); + initializer.provide(Size.class, -8); + } + + @Test + public void shouldThrowForNullValueAssociatedToAnnotation() { + // given / when / then + expectedException.expect(NullPointerException.class); + initializer.provide(Duration.class, null); + } + + @Test + public void shouldThrowForRegisterWithNull() { + // given / when / then + expectedException.expect(NullPointerException.class); + initializer.register(String.class, null); + } + + @Test + public void shouldExecutePostConstructMethod() { + // given + initializer.provide(Size.class, 15123); + + // when + PostConstructTestClass testClass = initializer.get(PostConstructTestClass.class); + + // then + assertThat(testClass.wasPostConstructCalled(), equalTo(true)); + assertThat(testClass.getBetaManager(), not(nullValue())); + } + + @Test + public void shouldThrowForInvalidPostConstructMethod() { + // given / when / then + expectRuntimeExceptionWith("@PostConstruct method may not be static or have any parameters"); + initializer.get(InvalidPostConstruct.WithParams.class); + } + + @Test + public void shouldThrowForStaticPostConstructMethod() { + // given / when / then + expectRuntimeExceptionWith("@PostConstruct method may not be static or have any parameters"); + initializer.get(InvalidPostConstruct.Static.class); + } + + @Test + public void shouldForwardExceptionFromPostConstruct() { + // given / when / then + expectRuntimeExceptionWith("Error executing @PostConstruct method"); + initializer.get(InvalidPostConstruct.ThrowsException.class); + } + + @Test + public void shouldThrowForMultiplePostConstructMethods() { + // given / when / then + expectRuntimeExceptionWith("Multiple methods with @PostConstruct"); + initializer.get(InvalidPostConstruct.MultiplePostConstructs.class); + } + + @Test + public void shouldThrowForPostConstructNotReturningVoid() { + // given / when / then + expectRuntimeExceptionWith("@PostConstruct method must have return type void"); + initializer.get(InvalidPostConstruct.NotVoidReturnType.class); + } + + @Test + public void shouldThrowForAbstractNonRegisteredDependency() { + // given / when / then + expectRuntimeExceptionWith("cannot be instantiated"); + initializer.get(ClassWithAbstractDependency.class); + } + + @Test + public void shouldInstantiateWithImplementationOfAbstractDependency() { + // given + ClassWithAbstractDependency.ConcreteDependency concrete = new ClassWithAbstractDependency.ConcreteDependency(); + initializer.register(ClassWithAbstractDependency.AbstractDependency.class, concrete); + + // when + ClassWithAbstractDependency cwad = initializer.get(ClassWithAbstractDependency.class); + + // then + assertThat(cwad.getAbstractDependency() == concrete, equalTo(true)); + assertThat(cwad.getAlphaService(), not(nullValue())); + } + + @Test + public void shouldThrowForAlreadyRegisteredClass() { + // given + initializer.register(BetaManager.class, new BetaManager()); + + // when / then + expectRuntimeExceptionWith("There is already an object present"); + initializer.register(BetaManager.class, new BetaManager()); + } + + @Test + public void shouldCreateNewUntrackedInstance() { + // given / when + AlphaService singletonScoped = initializer.get(AlphaService.class); + AlphaService requestScoped = initializer.newInstance(AlphaService.class); + + // then + assertThat(singletonScoped.getProvidedClass(), not(nullValue())); + assertThat(singletonScoped.getProvidedClass(), equalTo(requestScoped.getProvidedClass())); + assertThat(singletonScoped, not(sameInstance(requestScoped))); + } + + @Test + public void shouldThrowForStaticFieldInjection() { + // given / when / then + expectRuntimeExceptionWith("is static but annotated with @Inject"); + initializer.newInstance(InvalidStaticFieldInjection.class); + } + + @Test + public void shouldFallbackToSimpleInstantiationForPlainClass() { + // given / when + InstantiationFallbackClasses.HasFallbackDependency result = + initializer.get(InstantiationFallbackClasses.HasFallbackDependency.class); + + // then + assertThat(result, not(nullValue())); + assertThat(result.getGammaService(), not(nullValue())); + assertThat(result.getFallbackDependency(), not(nullValue())); + } + + @Test + public void shouldPerformReloadOnApplicableInstances() { + // given + initializer.provide(Size.class, 12); + initializer.provide(Duration.class, -113L); + initializer.register(NewSetting.class, mock(NewSetting.class)); + + GammaService gammaService = initializer.get(GammaService.class); + PostConstructTestClass postConstructTestClass = initializer.get(PostConstructTestClass.class); + ProvidedClass providedClass = initializer.get(ProvidedClass.class); + initializer.get(ClassWithAnnotations.class); + // Assert that no class was somehow reloaded at initialization + assertThat(gammaService.getWasReloaded() || postConstructTestClass.getWasReloaded() + || providedClass.getWasReloaded(), equalTo(false)); + + // when + initializer.performReloadOnServices(); + + // then + assertThat(gammaService.getWasReloaded(), equalTo(true)); + assertThat(postConstructTestClass.getWasReloaded(), equalTo(true)); + assertThat(providedClass.getWasReloaded(), equalTo(true)); + } + + @Test + public void shouldThrowForNullSetting() { + // given / when / then + expectRuntimeExceptionWith("Settings instance is null"); + initializer.performReloadOnServices(); + } + + @Test + public void shouldRetrieveExistingInstancesOnly() { + // given + initializer.get(GammaService.class); + + // when + AlphaService alphaService = initializer.getIfAvailable(AlphaService.class); + BetaManager betaManager = initializer.getIfAvailable(BetaManager.class); + + // then + // was initialized because is dependency of GammaService + assertThat(alphaService, not(nullValue())); + // nothing caused this to be initialized + assertThat(betaManager, nullValue()); + } + + private void expectRuntimeExceptionWith(String message) { + expectedException.expect(RuntimeException.class); + expectedException.expectMessage(containsString(message)); + } + +} diff --git a/src/test/java/fr/xephi/authme/initialization/ConstructorInjectionTest.java b/src/test/java/fr/xephi/authme/initialization/ConstructorInjectionTest.java new file mode 100644 index 00000000..cc55b4c3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/ConstructorInjectionTest.java @@ -0,0 +1,83 @@ +package fr.xephi.authme.initialization; + +import fr.xephi.authme.initialization.samples.AlphaService; +import fr.xephi.authme.initialization.samples.BetaManager; +import fr.xephi.authme.initialization.samples.ClassWithAnnotations; +import fr.xephi.authme.initialization.samples.Duration; +import fr.xephi.authme.initialization.samples.GammaService; +import fr.xephi.authme.initialization.samples.InvalidClass; +import fr.xephi.authme.initialization.samples.ProvidedClass; +import fr.xephi.authme.initialization.samples.Size; +import org.junit.Test; + +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link ConstructorInjection}. + */ +public class ConstructorInjectionTest { + + @SuppressWarnings("unchecked") + @Test + public void shouldReturnDependencies() { + // given + Injection injection = ConstructorInjection.provide(ClassWithAnnotations.class).get(); + + // when + Class[] dependencies = injection.getDependencies(); + Class[] annotations = injection.getDependencyAnnotations(); + + // then + assertThat(dependencies, arrayContaining(int.class, GammaService.class, long.class)); + assertThat(annotations, arrayContaining((Class) Size.class, null, Duration.class)); + } + + @Test + public void shouldInstantiate() { + // given + GammaService gammaService = new GammaService( + AlphaService.newInstance(new ProvidedClass(""))); + Injection injection = ConstructorInjection.provide(ClassWithAnnotations.class).get(); + + // when + ClassWithAnnotations instance = injection.instantiateWith(-112, gammaService, 19L); + + // then + assertThat(instance, not(nullValue())); + assertThat(instance.getSize(), equalTo(-112)); + assertThat(instance.getGammaService(), equalTo(gammaService)); + assertThat(instance.getDuration(), equalTo(19L)); + } + + @Test(expected = NullPointerException.class) + public void shouldThrowForNullValue() { + // given + Injection injection = ConstructorInjection.provide(ClassWithAnnotations.class).get(); + + // when / then + injection.instantiateWith(-112, null, 12L); + } + + @Test(expected = RuntimeException.class) + public void shouldThrowUponInstantiationError() { + // given + AlphaService alphaService = AlphaService.newInstance(new ProvidedClass("")); + Injection injection = ConstructorInjection.provide(InvalidClass.class).get(); + + // when + injection.instantiateWith(alphaService, 5); + } + + @Test + public void shouldReturnNullForNoConstructorInjection() { + // given / when + Injection injection = ConstructorInjection.provide(BetaManager.class).get(); + + // then + assertThat(injection, nullValue()); + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/FieldInjectionTest.java b/src/test/java/fr/xephi/authme/initialization/FieldInjectionTest.java new file mode 100644 index 00000000..89e460e5 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/FieldInjectionTest.java @@ -0,0 +1,132 @@ +package fr.xephi.authme.initialization; + +import fr.xephi.authme.initialization.samples.AlphaService; +import fr.xephi.authme.initialization.samples.BadFieldInjection; +import fr.xephi.authme.initialization.samples.BetaManager; +import fr.xephi.authme.initialization.samples.ClassWithAnnotations; +import fr.xephi.authme.initialization.samples.Duration; +import fr.xephi.authme.initialization.samples.FieldInjectionWithAnnotations; +import fr.xephi.authme.initialization.samples.GammaService; +import fr.xephi.authme.initialization.samples.InvalidStaticFieldInjection; +import fr.xephi.authme.initialization.samples.ProvidedClass; +import fr.xephi.authme.initialization.samples.Size; +import org.junit.Test; + +import javax.inject.Inject; + +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link FieldInjection}. + */ + +public class FieldInjectionTest { + + @SuppressWarnings("unchecked") + @Test + public void shouldReturnDependencies() { + // given + FieldInjection injection = + FieldInjection.provide(FieldInjectionWithAnnotations.class).get(); + + // when + Class[] dependencies = injection.getDependencies(); + Class[] annotations = injection.getDependencyAnnotations(); + + // then + assertThat(dependencies, arrayContaining(BetaManager.class, int.class, long.class, ClassWithAnnotations.class)); + assertThat(annotations, arrayContaining((Class) null, Size.class, Duration.class, null)); + } + + @Test + public void shouldInstantiateClass() { + // given + FieldInjection injection = FieldInjection.provide(BetaManager.class).get(); + ProvidedClass providedClass = new ProvidedClass(""); + AlphaService alphaService = AlphaService.newInstance(providedClass); + GammaService gammaService = new GammaService(alphaService); + + // when + BetaManager betaManager = injection.instantiateWith(providedClass, gammaService, alphaService); + + // then + assertThat(betaManager, not(nullValue())); + assertThat(betaManager.getDependencies(), arrayContaining(providedClass, gammaService, alphaService)); + } + + @Test + public void shouldProvideNullForImpossibleFieldInjection() { + // given / when + FieldInjection injection = FieldInjection.provide(BadFieldInjection.class).get(); + + // then + assertThat(injection, nullValue()); + } + + @Test(expected = RuntimeException.class) + public void shouldForwardExceptionDuringInstantiation() { + // given + FieldInjection injection = FieldInjection.provide(ThrowingConstructor.class).get(); + + // when / when + injection.instantiateWith(new ProvidedClass("")); + } + + @Test(expected = RuntimeException.class) + public void shouldThrowForInvalidFieldValue() { + // given + ProvidedClass providedClass = new ProvidedClass(""); + AlphaService alphaService = AlphaService.newInstance(providedClass); + GammaService gammaService = new GammaService(alphaService); + FieldInjection injection = FieldInjection.provide(BetaManager.class).get(); + + // when / then + // Correct order is provided, gamma, alpha + injection.instantiateWith(providedClass, alphaService, gammaService); + } + + @Test(expected = NullPointerException.class) + public void shouldThrowForNullValue() { + // given + ProvidedClass providedClass = new ProvidedClass(""); + AlphaService alphaService = AlphaService.newInstance(providedClass); + FieldInjection injection = FieldInjection.provide(BetaManager.class).get(); + + // when / then + // Correct order is provided, gamma, alpha + injection.instantiateWith(providedClass, null, alphaService); + } + + @Test(expected = RuntimeException.class) + public void shouldThrowForStaticFieldInjection() { + // given / when / then + FieldInjection.provide(InvalidStaticFieldInjection.class).get(); + } + + @Test + public void shouldNotReturnFieldInjectionForZeroInjectFields() { + // given / when + Injection injection = FieldInjection.provide(NoInjectionClass.class).get(); + + // then + assertThat(injection, nullValue()); + } + + + @SuppressWarnings("unused") + private static class ThrowingConstructor { + @Inject + private ProvidedClass providedClass; + + public ThrowingConstructor() { + throw new UnsupportedOperationException("Exception in constructor"); + } + } + + private static class NoInjectionClass { + + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/InstantiationFallbackTest.java b/src/test/java/fr/xephi/authme/initialization/InstantiationFallbackTest.java new file mode 100644 index 00000000..2676c404 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/InstantiationFallbackTest.java @@ -0,0 +1,78 @@ +package fr.xephi.authme.initialization; + +import fr.xephi.authme.initialization.samples.GammaService; +import fr.xephi.authme.initialization.samples.InstantiationFallbackClasses; +import org.junit.Test; + +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link InstantiationFallback}. + */ +public class InstantiationFallbackTest { + + @Test + public void shouldInstantiateClass() { + // given + Injection instantiation = + InstantiationFallback.provide(InstantiationFallbackClasses.FallbackClass.class).get(); + + // when + InstantiationFallbackClasses.FallbackClass result = instantiation.instantiateWith(); + + // then + assertThat(result, not(nullValue())); + } + + @Test(expected = RuntimeException.class) + public void shouldThrowIfArgumentsAreSupplied() { + // given + Injection instantiation = + InstantiationFallback.provide(InstantiationFallbackClasses.FallbackClass.class).get(); + + // when / then + instantiation.instantiateWith("some argument"); + } + + @Test + public void shouldReturnNullForClassWithInjectMethod() { + // given / when + Injection instantiation = + InstantiationFallback.provide(InstantiationFallbackClasses.InvalidInjectOnMethodClass.class).get(); + + // then + assertThat(instantiation, nullValue()); + } + + @Test + public void shouldReturnNullForMissingNoArgsConstructor() { + // given / when + Injection instantiation = + InstantiationFallback.provide(InstantiationFallbackClasses.InvalidFallbackClass.class).get(); + + // then + assertThat(instantiation, nullValue()); + } + + @Test + public void shouldReturnNullForDifferentInjectionType() { + // given / when + Injection instantiation = InstantiationFallback.provide(GammaService.class).get(); + + // then + assertThat(instantiation, nullValue()); + } + + @Test + public void shouldReturnNullForClassWithPostConstruct() { + // given / when + Injection instantiation = + InstantiationFallback.provide(InstantiationFallbackClasses.ClassWithPostConstruct.class).get(); + + // then + assertThat(instantiation, nullValue()); + } + +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/AlphaService.java b/src/test/java/fr/xephi/authme/initialization/samples/AlphaService.java new file mode 100644 index 00000000..21dd3b32 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/AlphaService.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Sample - class with dependency to ProvidedClass. + */ +public class AlphaService { + + private ProvidedClass providedClass; + + @Inject + AlphaService(ProvidedClass providedClass) { + this.providedClass = providedClass; + } + + public ProvidedClass getProvidedClass() { + return providedClass; + } + + /** + * Creates a new instance (for instantiations in tests). + * + * @param providedClass . + * @return created instance + */ + public static AlphaService newInstance(ProvidedClass providedClass) { + return new AlphaService(providedClass); + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/BadFieldInjection.java b/src/test/java/fr/xephi/authme/initialization/samples/BadFieldInjection.java new file mode 100644 index 00000000..7e218c4c --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/BadFieldInjection.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Sample class with invalid field injection (requires default constructor). + */ +public class BadFieldInjection { + + @SuppressWarnings("unused") + @Inject + private AlphaService alphaService; + + public BadFieldInjection(BetaManager betaManager) { + throw new IllegalStateException("Should never be called"); + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/BetaManager.java b/src/test/java/fr/xephi/authme/initialization/samples/BetaManager.java new file mode 100644 index 00000000..8c2fe923 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/BetaManager.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Sample - depends on Provided, alpha and gamma. + */ +public class BetaManager { + + @Inject + private ProvidedClass providedClass; + @Inject + private GammaService gammaService; + @Inject + private AlphaService alphaService; + + public Object[] getDependencies() { + return new Object[]{providedClass, gammaService, alphaService}; + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/CircularClasses.java b/src/test/java/fr/xephi/authme/initialization/samples/CircularClasses.java new file mode 100644 index 00000000..dc2313e3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/CircularClasses.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Classes with circular dependencies. + */ +public abstract class CircularClasses { + + public static final class Circular1 { + @Inject + public Circular1(AlphaService alphaService, Circular3 circular3) { + // -- + } + } + + public static final class Circular2 { + @Inject + public Circular2(Circular1 circular1) { + // -- + } + } + + public static final class Circular3 { + @Inject + public Circular3(Circular2 circular2, BetaManager betaManager) { + // -- + } + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/ClassWithAbstractDependency.java b/src/test/java/fr/xephi/authme/initialization/samples/ClassWithAbstractDependency.java new file mode 100644 index 00000000..25b651b3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/ClassWithAbstractDependency.java @@ -0,0 +1,32 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Test with an abstract class declared as dependency. + */ +public class ClassWithAbstractDependency { + + private final AlphaService alphaService; + private final AbstractDependency abstractDependency; + + @Inject + public ClassWithAbstractDependency(AlphaService as, AbstractDependency ad) { + this.alphaService = as; + this.abstractDependency = ad; + } + + public AlphaService getAlphaService() { + return alphaService; + } + + public AbstractDependency getAbstractDependency() { + return abstractDependency; + } + + public static abstract class AbstractDependency { + } + + public static final class ConcreteDependency extends AbstractDependency { + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/ClassWithAnnotations.java b/src/test/java/fr/xephi/authme/initialization/samples/ClassWithAnnotations.java new file mode 100644 index 00000000..61dca71b --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/ClassWithAnnotations.java @@ -0,0 +1,29 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +public class ClassWithAnnotations { + + private int size; + private GammaService gammaService; + private long duration; + + @Inject + ClassWithAnnotations(@Size int size, GammaService gammaService, @Duration long duration) { + this.size = size; + this.gammaService = gammaService; + this.duration = duration; + } + + public int getSize() { + return size; + } + + public GammaService getGammaService() { + return gammaService; + } + + public long getDuration() { + return duration; + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/Duration.java b/src/test/java/fr/xephi/authme/initialization/samples/Duration.java new file mode 100644 index 00000000..e61c6d28 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/Duration.java @@ -0,0 +1,14 @@ +package fr.xephi.authme.initialization.samples; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Sample annotation. + */ +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Duration { +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/FieldInjectionWithAnnotations.java b/src/test/java/fr/xephi/authme/initialization/samples/FieldInjectionWithAnnotations.java new file mode 100644 index 00000000..65bdf537 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/FieldInjectionWithAnnotations.java @@ -0,0 +1,40 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Sample - field injection, including custom annotations. + */ +public class FieldInjectionWithAnnotations { + + @Inject + private BetaManager betaManager; + @Inject + @Size + private int size; + @Duration + @Inject + private long duration; + @Inject + protected ClassWithAnnotations classWithAnnotations; + + FieldInjectionWithAnnotations() { + } + + public BetaManager getBetaManager() { + return betaManager; + } + + public int getSize() { + return size; + } + + public long getDuration() { + return duration; + } + + public ClassWithAnnotations getClassWithAnnotations() { + return classWithAnnotations; + } + +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/GammaService.java b/src/test/java/fr/xephi/authme/initialization/samples/GammaService.java new file mode 100644 index 00000000..158187ea --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/GammaService.java @@ -0,0 +1,32 @@ +package fr.xephi.authme.initialization.samples; + +import fr.xephi.authme.initialization.Reloadable; + +import javax.inject.Inject; + +/** + * Sample - class dependent on alpha service. + */ +public class GammaService implements Reloadable { + + private AlphaService alphaService; + private boolean wasReloaded; + + @Inject + public GammaService(AlphaService alphaService) { + this.alphaService = alphaService; + } + + public AlphaService getAlphaService() { + return alphaService; + } + + @Override + public void reload() { + wasReloaded = true; + } + + public boolean getWasReloaded() { + return wasReloaded; + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/InstantiationFallbackClasses.java b/src/test/java/fr/xephi/authme/initialization/samples/InstantiationFallbackClasses.java new file mode 100644 index 00000000..c7fbd7e2 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/InstantiationFallbackClasses.java @@ -0,0 +1,54 @@ +package fr.xephi.authme.initialization.samples; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +/** + * Sample class - tests various situations for the instantiation fallback. + */ +public abstract class InstantiationFallbackClasses { + + public static final class FallbackClass { + // No @Inject annotations, public no-args constructor + } + + public static final class HasFallbackDependency { + @Inject + private FallbackClass fallbackClass; + + @Inject + private GammaService gammaService; + + public GammaService getGammaService() { + return gammaService; + } + + public FallbackClass getFallbackDependency() { + return fallbackClass; + } + } + + public static final class InvalidFallbackClass { + private InvalidFallbackClass() { + // no-args constructor must be public for fallback instantiation + } + } + + public static final class InvalidInjectOnMethodClass { + // We don't support method injection but this should still be detected and an exception returned + // Only use instantiation fallback if we're sure there isn't some sort of misconfiguration + @Inject + public void setGammaService(GammaService gammaService) { + // -- + } + } + + // Class with @PostConstruct method should never be instantiated by instantiation fallback + public static final class ClassWithPostConstruct { + @PostConstruct + public void postConstructMethod() { + // -- + } + } + +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/InvalidClass.java b/src/test/java/fr/xephi/authme/initialization/samples/InvalidClass.java new file mode 100644 index 00000000..b896af9d --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/InvalidClass.java @@ -0,0 +1,14 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Sample - invalid class, since Integer parameter type is outside of the allowed package and not annotated. + */ +public class InvalidClass { + + @Inject + public InvalidClass(AlphaService alphaService, Integer i) { + throw new IllegalStateException("Should never be called"); + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/InvalidPostConstruct.java b/src/test/java/fr/xephi/authme/initialization/samples/InvalidPostConstruct.java new file mode 100644 index 00000000..1e832549 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/InvalidPostConstruct.java @@ -0,0 +1,71 @@ +package fr.xephi.authme.initialization.samples; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +/** + * Class with invalid @PostConstruct method. + */ + +@SuppressWarnings("unused") +public abstract class InvalidPostConstruct { + + public static final class WithParams { + @Inject + private AlphaService alphaService; + @Inject + private ProvidedClass providedClass; + + WithParams() { } + + @PostConstruct + public void invalidPostConstr(BetaManager betaManager) { + } + } + + public static final class Static { + @Inject + Static(BetaManager betaManager) { + // -- + } + + @PostConstruct + public static void invalidMethod() { + // -- + } + } + + public static final class ThrowsException { + @Inject + private ProvidedClass providedClass; + + @PostConstruct + public void throwingPostConstruct() { + throw new IllegalStateException("Exception in post construct"); + } + } + + public static final class NotVoidReturnType { + @Inject + private ProvidedClass providedClass; + + @PostConstruct + public int returnsInt() { + return 42; + } + } + + public static final class MultiplePostConstructs { + @Inject + private ProvidedClass providedClass; + + @PostConstruct + public void postConstruct1() { + // -- + } + @PostConstruct + public void postConstruct2() { + // -- + } + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/InvalidStaticFieldInjection.java b/src/test/java/fr/xephi/authme/initialization/samples/InvalidStaticFieldInjection.java new file mode 100644 index 00000000..83d88b0c --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/InvalidStaticFieldInjection.java @@ -0,0 +1,18 @@ +package fr.xephi.authme.initialization.samples; + +import javax.inject.Inject; + +/** + * Sample class - attempted field injection on a static member. + */ +public class InvalidStaticFieldInjection { + + @SuppressWarnings("unused") + @Inject + private ProvidedClass providedClass; + @Inject + protected static AlphaService alphaService; + + InvalidStaticFieldInjection() { } + +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/PostConstructTestClass.java b/src/test/java/fr/xephi/authme/initialization/samples/PostConstructTestClass.java new file mode 100644 index 00000000..a8b9e64e --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/PostConstructTestClass.java @@ -0,0 +1,45 @@ +package fr.xephi.authme.initialization.samples; + +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.settings.NewSetting; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +/** + * Sample class for testing the execution of @PostConstruct methods. + */ +public class PostConstructTestClass implements SettingsDependent { + + @Inject + @Size + private int size; + @Inject + private BetaManager betaManager; + private boolean wasPostConstructCalled = false; + private boolean wasReloaded = false; + + @PostConstruct + public void postConstructMethod() { + wasPostConstructCalled = true; + } + + public boolean wasPostConstructCalled() { + return wasPostConstructCalled; + } + + public BetaManager getBetaManager() { + return betaManager; + } + + @Override + public void loadSettings(NewSetting settings) { + if (settings != null) { + wasReloaded = true; + } + } + + public boolean getWasReloaded() { + return wasReloaded; + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/ProvidedClass.java b/src/test/java/fr/xephi/authme/initialization/samples/ProvidedClass.java new file mode 100644 index 00000000..d3405abe --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/ProvidedClass.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.initialization.samples; + +import fr.xephi.authme.initialization.Reloadable; + +import javax.inject.Inject; + +/** + * Sample - class that is always provided to the initializer beforehand. + */ +public class ProvidedClass implements Reloadable { + + private boolean wasReloaded = false; + + @Inject + public ProvidedClass() { + throw new IllegalStateException("Should never be called (tests always provide this class)"); + } + + public ProvidedClass(String manualConstructor) { + } + + @Override + public void reload() { + wasReloaded = true; + } + + public boolean getWasReloaded() { + return wasReloaded; + } +} diff --git a/src/test/java/fr/xephi/authme/initialization/samples/Size.java b/src/test/java/fr/xephi/authme/initialization/samples/Size.java new file mode 100644 index 00000000..320a7b7c --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/samples/Size.java @@ -0,0 +1,14 @@ +package fr.xephi.authme.initialization.samples; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Sample annotation. + */ +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Size { +} diff --git a/src/test/java/fr/xephi/authme/listener/AuthMeBlockListenerTest.java b/src/test/java/fr/xephi/authme/listener/AuthMeBlockListenerTest.java new file mode 100644 index 00000000..e96a3022 --- /dev/null +++ b/src/test/java/fr/xephi/authme/listener/AuthMeBlockListenerTest.java @@ -0,0 +1,95 @@ +package fr.xephi.authme.listener; + +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +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 AuthMeBlockListener}. + */ +@RunWith(MockitoJUnitRunner.class) +public class AuthMeBlockListenerTest { + + @InjectMocks + private AuthMeBlockListener listener; + + @Mock + private ListenerService listenerService; + + @Test + public void shouldAllowPlaceEvent() { + // given + Player player = mock(Player.class); + BlockPlaceEvent event = mock(BlockPlaceEvent.class); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(false); + + // when + listener.onBlockPlace(event); + + // then + verify(event).getPlayer(); + verifyNoMoreInteractions(event); + } + + @Test + public void shouldDenyPlaceEvent() { + // given + Player player = mock(Player.class); + BlockPlaceEvent event = mock(BlockPlaceEvent.class); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(true); + + // when + listener.onBlockPlace(event); + + // then + verify(event).setCancelled(true); + verify(event).getPlayer(); + verifyNoMoreInteractions(event); + } + + @Test + public void shouldAllowBreakEvent() { + // given + Player player = mock(Player.class); + BlockBreakEvent event = mock(BlockBreakEvent.class); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(false); + + // when + listener.onBlockBreak(event); + + // then + verify(event).getPlayer(); + verifyNoMoreInteractions(event); + } + + @Test + public void shouldDenyBreakEvent() { + // given + Player player = mock(Player.class); + BlockBreakEvent event = mock(BlockBreakEvent.class); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(true); + + // when + listener.onBlockBreak(event); + + // then + verify(event).setCancelled(true); + verify(event).getPlayer(); + verifyNoMoreInteractions(event); + } + +} diff --git a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java index 5fe3ce26..4749dac4 100644 --- a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java @@ -106,7 +106,11 @@ public final class ListenerConsistencyTest { // Exclude any methods with "$" in it: jacoco creates a "$jacocoInit" method we want to ignore, and // methods like "access$000" are created by the compiler when a private member is being accessed by an inner // class, which is not of interest for us - return !Modifier.isPrivate(method.getModifiers()) && !method.getName().contains("$"); + if (Modifier.isPrivate(method.getModifiers()) || method.getName().contains("$")) { + return false; + } + // Skip reload() method (implementation of Reloadable interface) + return !"reload".equals(method.getName()) || method.getParameterTypes().length > 0; } } diff --git a/src/test/java/fr/xephi/authme/listener/ListenerServiceTest.java b/src/test/java/fr/xephi/authme/listener/ListenerServiceTest.java new file mode 100644 index 00000000..6b87ffe1 --- /dev/null +++ b/src/test/java/fr/xephi/authme/listener/ListenerServiceTest.java @@ -0,0 +1,255 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.hooks.PluginHooks; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.player.PlayerEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Method; +import java.util.Arrays; + +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.verifyZeroInteractions; + +/** + * Test for {@link ListenerService}. + */ +@RunWith(MockitoJUnitRunner.class) +public class ListenerServiceTest { + + private ListenerService listenerService; + + @Mock + private NewSetting settings; + + @Mock + private DataSource dataSource; + + @Mock + private PluginHooks pluginHooks; + + @Mock + private PlayerCache playerCache; + + @SuppressWarnings("rawtypes") + @Before + public void initializeTestSetup() { + given(settings.getProperty(RegistrationSettings.FORCE)).willReturn(true); + given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn( + Arrays.asList("npc1", "npc2", "npc3")); + + // Note ljacqu 20160602: We use a hacky way to avoid having to instantiate the service in each test: + // the listenerService test is initialized as a mock that will answer to any method invocation by creating an + // actual service object (with the @Mock fields) and then invoking the method on that actual service. + // As long as there is no interaction with listenerService all of the mock setups will have effect. + listenerService = mock(ListenerService.class, new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Method method = invocation.getMethod(); + ListenerService service = new ListenerService(settings, dataSource, pluginHooks, playerCache); + return method.invoke(service, invocation.getArguments()); + } + }); + } + + @Test + public void shouldHandleEventWithNullEntity() { + // given + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(null); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldHandleEntityEventWithNonPlayerEntity() { + // given + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(mock(Entity.class)); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldAllowAuthenticatedPlayer() { + // given + String playerName = "Bobby"; + Player player = mockPlayerWithName(playerName); + given(playerCache.isAuthenticated(playerName)).willReturn(true); + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(player); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + verify(playerCache).isAuthenticated(playerName); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldDenyUnLoggedPlayer() { + // given + String playerName = "Tester"; + Player player = mockPlayerWithName(playerName); + given(playerCache.isAuthenticated(playerName)).willReturn(false); + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(player); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(true)); + verify(playerCache).isAuthenticated(playerName); + // makes sure the setting is checked first = avoid unnecessary DB operation + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldAllowUnloggedPlayerForOptionalRegistration() { + // given + String playerName = "myPlayer1"; + Player player = mockPlayerWithName(playerName); + given(playerCache.isAuthenticated(playerName)).willReturn(false); + given(settings.getProperty(RegistrationSettings.FORCE)).willReturn(false); + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(player); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + verify(playerCache).isAuthenticated(playerName); + verify(dataSource).isAuthAvailable(playerName); + } + + @Test + public void shouldAllowUnrestrictedName() { + // given + String playerName = "Npc2"; + Player player = mockPlayerWithName(playerName); + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(player); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldAllowNpcPlayer() { + // given + String playerName = "other_npc"; + Player player = mockPlayerWithName(playerName); + EntityEvent event = mock(EntityEvent.class); + given(event.getEntity()).willReturn(player); + given(pluginHooks.isNpc(player)).willReturn(true); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + verify(pluginHooks).isNpc(player); + } + + @Test + // This simply forwards to shouldCancelEvent(Player), so the rest is already tested + public void shouldHandlePlayerEvent() { + // given + String playerName = "example"; + Player player = mockPlayerWithName(playerName); + PlayerEvent event = new TestPlayerEvent(player); + given(playerCache.isAuthenticated(playerName)).willReturn(true); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + verify(playerCache).isAuthenticated(playerName); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldHandlePlayerEventWithNullPlayer() { + // given + PlayerEvent event = new TestPlayerEvent(null); + + // when + boolean result = listenerService.shouldCancelEvent(event); + + // then + assertThat(result, equalTo(false)); + } + + @Test + // The previous tests verify most of shouldCancelEvent(Player) + public void shouldVerifyBasedOnPlayer() { + // given + String playerName = "player"; + Player player = mockPlayerWithName(playerName); + + // when + boolean result = listenerService.shouldCancelEvent(player); + + // then + assertThat(result, equalTo(true)); + verify(playerCache).isAuthenticated(playerName); + verifyZeroInteractions(dataSource); + verify(pluginHooks).isNpc(player); + } + + private static Player mockPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } + + /** + * Test implementation of {@link PlayerEvent} (necessary because + * {@link PlayerEvent#getPlayer()} is declared final). + */ + private static final class TestPlayerEvent extends PlayerEvent { + public TestPlayerEvent(Player player) { + super(player); + } + + @Override + public HandlerList getHandlers() { + return null; + } + } +} diff --git a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java new file mode 100644 index 00000000..6d5d27e4 --- /dev/null +++ b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java @@ -0,0 +1,413 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.AntiBot; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.ValidationService; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerLoginEvent; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +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; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link OnJoinVerifier}. + */ +@RunWith(MockitoJUnitRunner.class) +public class OnJoinVerifierTest { + + @InjectMocks + private OnJoinVerifier onJoinVerifier; + + @Mock + private NewSetting settings; + @Mock + private DataSource dataSource; + @Mock + private Messages messages; + @Mock + private PermissionsManager permissionsManager; + @Mock + private AntiBot antiBot; + @Mock + private ValidationService validationService; + @Mock + private BukkitService bukkitService; + @Mock + private Server server; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @BeforeClass + public static void setUpLogger() { + TestHelper.setupLogger(); + } + + @Test + public void shouldNotDoAnythingForNormalEvent() { + // given + PlayerLoginEvent event = mock(PlayerLoginEvent.class); + given(event.getResult()).willReturn(PlayerLoginEvent.Result.ALLOWED); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(false)); + verify(event).getResult(); + verifyNoMoreInteractions(event); + verifyZeroInteractions(bukkitService); + verifyZeroInteractions(dataSource); + verifyZeroInteractions(permissionsManager); + } + + @Test + public void shouldRefuseNonVipPlayerForFullServer() { + // given + Player player = mock(Player.class); + PlayerLoginEvent event = new PlayerLoginEvent(player, "hostname", null); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + given(permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)).willReturn(false); + String serverFullMessage = "server is full"; + given(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)).willReturn(serverFullMessage); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(true)); + assertThat(event.getResult(), equalTo(PlayerLoginEvent.Result.KICK_FULL)); + assertThat(event.getKickMessage(), equalTo(serverFullMessage)); + verifyZeroInteractions(bukkitService); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldKickNonVipForJoiningVipPlayer() { + // given + Player player = mock(Player.class); + PlayerLoginEvent event = new PlayerLoginEvent(player, "hostname", null); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + given(permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)).willReturn(true); + List onlinePlayers = Arrays.asList(mock(Player.class), mock(Player.class)); + given(permissionsManager.hasPermission(onlinePlayers.get(0), PlayerStatePermission.IS_VIP)).willReturn(true); + given(permissionsManager.hasPermission(onlinePlayers.get(1), PlayerStatePermission.IS_VIP)).willReturn(false); + returnOnlineListFromBukkitServer(onlinePlayers); + given(server.getMaxPlayers()).willReturn(onlinePlayers.size()); + given(messages.retrieveSingle(MessageKey.KICK_FOR_VIP)).willReturn("kick for vip"); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(false)); + assertThat(event.getResult(), equalTo(PlayerLoginEvent.Result.ALLOWED)); + // First player is VIP, so expect no interactions there and second player to have been kicked + verifyZeroInteractions(onlinePlayers.get(0)); + verify(onlinePlayers.get(1)).kickPlayer("kick for vip"); + } + + @Test + public void shouldKickVipPlayerIfNoPlayerCanBeKicked() { + // given + Player player = mock(Player.class); + PlayerLoginEvent event = new PlayerLoginEvent(player, "hostname", null); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + given(permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)).willReturn(true); + List onlinePlayers = Collections.singletonList(mock(Player.class)); + given(permissionsManager.hasPermission(onlinePlayers.get(0), PlayerStatePermission.IS_VIP)).willReturn(true); + returnOnlineListFromBukkitServer(onlinePlayers); + given(server.getMaxPlayers()).willReturn(onlinePlayers.size()); + given(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)).willReturn("kick full server"); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(true)); + assertThat(event.getResult(), equalTo(PlayerLoginEvent.Result.KICK_FULL)); + assertThat(event.getKickMessage(), equalTo("kick full server")); + verifyZeroInteractions(onlinePlayers.get(0)); + } + + @Test + public void shouldKickNonRegistered() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)).willReturn(true); + + // expect + expectValidationExceptionWith(MessageKey.MUST_REGISTER_MESSAGE); + + // when + onJoinVerifier.checkKickNonRegistered(false); + } + + @Test + public void shouldNotKickRegisteredPlayer() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)).willReturn(true); + + // when + onJoinVerifier.checkKickNonRegistered(true); + } + + @Test + public void shouldNotKickUnregisteredPlayer() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)).willReturn(false); + + // when + onJoinVerifier.checkKickNonRegistered(false); + } + + @Test + public void shouldAllowValidName() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // when + onJoinVerifier.checkIsValidName("Bobby5"); + } + + @Test + public void shouldRejectTooLongName() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_LENGTH); + + // when + onJoinVerifier.checkIsValidName("longerthaneight"); + } + + @Test + public void shouldRejectTooShortName() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_LENGTH); + + // when + onJoinVerifier.checkIsValidName("abc"); + } + + @Test + public void shouldRejectNameWithInvalidCharacters() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_CHARACTERS, "[a-zA-Z0-9]+"); + + // when + onJoinVerifier.checkIsValidName("Tester!"); + } + + @Test + public void shouldAllowProperlyCasedName() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Bobby"); + PlayerAuth auth = PlayerAuth.builder().name("bobby").realName("Bobby").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldRejectNameWithWrongCasing() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Tester"); + PlayerAuth auth = PlayerAuth.builder().name("tester").realName("testeR").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_CASE, "testeR", "Tester"); + + // when / then + onJoinVerifier.checkNameCasing(player, auth); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldUpdateMissingRealName() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Authme"); + PlayerAuth auth = PlayerAuth.builder().name("authme").realName("").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verify(dataSource).updateRealName("authme", "Authme"); + } + + @Test + public void shouldUpdateDefaultRealName() throws FailedVerificationException { + // given + Player player = newPlayerWithName("SOMEONE"); + PlayerAuth auth = PlayerAuth.builder().name("someone").realName("Player").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verify(dataSource).updateRealName("someone", "SOMEONE"); + } + + @Test + public void shouldAcceptCasingMismatchForDisabledSetting() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Test"); + PlayerAuth auth = PlayerAuth.builder().name("test").realName("TEST").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(false); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldAcceptNameForUnregisteredAccount() throws FailedVerificationException { + // given + Player player = newPlayerWithName("MyPlayer"); + PlayerAuth auth = null; + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldAcceptNameThatIsNotOnline() throws FailedVerificationException { + // given + String name = "bobby"; + given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(true); + given(bukkitService.getPlayerExact("bobby")).willReturn(null); + + // when + onJoinVerifier.checkSingleSession(name); + + // then + verify(bukkitService).getPlayerExact(name); + } + + @Test + public void shouldRejectNameAlreadyOnline() throws FailedVerificationException { + // given + String name = "Charlie"; + Player onlinePlayer = newPlayerWithName("charlie"); + given(bukkitService.getPlayerExact("Charlie")).willReturn(onlinePlayer); + given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(true); + + // expect + expectValidationExceptionWith(MessageKey.USERNAME_ALREADY_ONLINE_ERROR); + + // when / then + onJoinVerifier.checkSingleSession(name); + } + + @Test + public void shouldAcceptAlreadyOnlineNameForDisabledSetting() throws FailedVerificationException { + // given + String name = "Felipe"; + given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(false); + + // when + onJoinVerifier.checkSingleSession(name); + + // then + verifyZeroInteractions(bukkitService); + } + + private static Player newPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void returnOnlineListFromBukkitServer(Collection onlineList) { + // Note ljacqu 20160529: The compiler gets lost in generics because Collection is returned + // from getOnlinePlayers(). We need to uncheck onlineList to a simple Collection or it will refuse to compile. + given(bukkitService.getOnlinePlayers()).willReturn((Collection) onlineList); + } + + private void expectValidationExceptionWith(MessageKey messageKey, String... args) { + expectedException.expect(exceptionWithData(messageKey, args)); + } + + private static Matcher exceptionWithData(final MessageKey messageKey, + final String... args) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(FailedVerificationException item) { + return messageKey.equals(item.getReason()) && Arrays.equals(args, item.getArgs()); + } + + @Override + public void describeTo(Description description) { + description.appendValue("VerificationFailedException: reason=" + messageKey + ";args=" + + (args == null ? "null" : StringUtils.join(", ", args))); + } + }; + } + +} diff --git a/src/test/java/fr/xephi/authme/output/MessagesIntegrationTest.java b/src/test/java/fr/xephi/authme/output/MessagesIntegrationTest.java index cd858ad8..5777d1d4 100644 --- a/src/test/java/fr/xephi/authme/output/MessagesIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/output/MessagesIntegrationTest.java @@ -2,6 +2,7 @@ package fr.xephi.authme.output; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.settings.NewSetting; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Before; @@ -19,6 +20,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.junit.Assume.assumeThat; +import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; @@ -247,9 +249,11 @@ public class MessagesIntegrationTest { MessageKey key = MessageKey.WRONG_PASSWORD; // assumption: message comes back as defined in messages_test.yml assumeThat(messages.retrieveSingle(key), equalTo("§cWrong password!")); + NewSetting settings = mock(NewSetting.class); + given(settings.getMessagesFile()).willReturn(TestHelper.getJarFile("/messages_test2.yml")); // when - messages.reload(TestHelper.getJarFile("/messages_test2.yml")); + messages.loadSettings(settings); // then assertThat(messages.retrieveSingle(key), equalTo("test2 - wrong password")); diff --git a/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java b/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java new file mode 100644 index 00000000..8ede82b6 --- /dev/null +++ b/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java @@ -0,0 +1,153 @@ +package fr.xephi.authme.permission; + +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link PermissionsManager}. + */ +@RunWith(MockitoJUnitRunner.class) +public class PermissionsManagerTest { + + @InjectMocks + private PermissionsManager permissionsManager; + + @Mock + private Server server; + + @Mock + private PluginManager pluginManager; + + @Test + public void shouldUseDefaultPermissionForCommandSender() { + // given + PermissionNode node = TestPermissions.LOGIN; + CommandSender sender = mock(CommandSender.class); + + // when + boolean result = permissionsManager.hasPermission(sender, node); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldGrantToOpCommandSender() { + // given + PermissionNode node = TestPermissions.DELETE_USER; + CommandSender sender = mock(CommandSender.class); + given(sender.isOp()).willReturn(true); + + // when + boolean result = permissionsManager.hasPermission(sender, node); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldDenyPermissionEvenForOpCommandSender() { + // given + PermissionNode node = TestPermissions.WORLD_DOMINATION; + CommandSender sender = mock(CommandSender.class); + given(sender.isOp()).willReturn(true); + + // when + boolean result = permissionsManager.hasPermission(sender, node); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldAllowForNonOpPlayer() { + // given + PermissionNode node = TestPermissions.LOGIN; + Player player = mock(Player.class); + + // when + boolean result = permissionsManager.hasPermission(player, node); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldDenyForNonOpPlayer() { + // given + PermissionNode node = TestPermissions.DELETE_USER; + Player player = mock(Player.class); + + // when + boolean result = permissionsManager.hasPermission(player, node); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldAllowForOpPlayer() { + // given + PermissionNode node = TestPermissions.DELETE_USER; + Player player = mock(Player.class); + given(player.isOp()).willReturn(true); + + // when + boolean result = permissionsManager.hasPermission(player, node); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldDenyEvenForOpPlayer() { + // given + PermissionNode node = TestPermissions.WORLD_DOMINATION; + Player player = mock(Player.class); + given(player.isOp()).willReturn(true); + + // when + boolean result = permissionsManager.hasPermission(player, node); + + // then + assertThat(result, equalTo(false)); + } + + @Test + public void shouldHandleNullPermissionForCommandSender() { + // given + PermissionNode node = null; + CommandSender sender = mock(CommandSender.class); + + // when + boolean result = permissionsManager.hasPermission(sender, node); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void shouldHandleNullPermissionForPlayer() { + // given + PermissionNode node = null; + Player player = mock(Player.class); + + // when + boolean result = permissionsManager.hasPermission(player, node); + + // then + assertThat(result, equalTo(true)); + } +} diff --git a/src/test/java/fr/xephi/authme/permission/PermissionsSystemTypeTest.java b/src/test/java/fr/xephi/authme/permission/PermissionsSystemTypeTest.java new file mode 100644 index 00000000..809014ad --- /dev/null +++ b/src/test/java/fr/xephi/authme/permission/PermissionsSystemTypeTest.java @@ -0,0 +1,45 @@ +package fr.xephi.authme.permission; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link PermissionsSystemType}. + */ +public class PermissionsSystemTypeTest { + + @Test + public void shouldHaveDefinedAndUniqueNames() { + // given / when / then + List names = new ArrayList<>(PermissionsSystemType.values().length); + List pluginNames = new ArrayList<>(PermissionsSystemType.values().length); + + for (PermissionsSystemType system : PermissionsSystemType.values()) { + assertThat("Name for enum entry '" + system + "' is not null", + system.getName(), not(nullValue())); + assertThat("Plugin name for enum entry '" + system + "' is not null", + system.getPluginName(), not(nullValue())); + assertThat("Only one enum entry has name '" + system.getName() + "'", + names, not(hasItem(system.getName()))); + assertThat("Only one enum entry has plugin name '" + system.getPluginName() + "'", + pluginNames, not(hasItem(system.getPluginName()))); + names.add(system.getName()); + pluginNames.add(system.getPluginName()); + } + } + + @Test + public void shouldRecognizePermissionSystemType() { + assertThat(PermissionsSystemType.isPermissionSystem("bogus"), equalTo(false)); + assertThat(PermissionsSystemType.isPermissionSystem("PermissionsBukkit"), equalTo(true)); + } + +} diff --git a/src/test/java/fr/xephi/authme/permission/TestPermissions.java b/src/test/java/fr/xephi/authme/permission/TestPermissions.java new file mode 100644 index 00000000..89460771 --- /dev/null +++ b/src/test/java/fr/xephi/authme/permission/TestPermissions.java @@ -0,0 +1,33 @@ +package fr.xephi.authme.permission; + +/** + * Sample permission nodes for testing. + */ +public enum TestPermissions implements PermissionNode { + + LOGIN("authme.login", DefaultPermission.ALLOWED), + + DELETE_USER("authme.admin.delete", DefaultPermission.OP_ONLY), + + WORLD_DOMINATION("global.own", DefaultPermission.NOT_ALLOWED); + + + private final String node; + private final DefaultPermission defaultPermission; + + TestPermissions(String node, DefaultPermission defaultPermission) { + this.node = node; + this.defaultPermission = defaultPermission; + } + + @Override + public String getNode() { + return node; + } + + @Override + public DefaultPermission getDefaultPermission() { + return defaultPermission; + } + +} diff --git a/src/test/java/fr/xephi/authme/process/ProcessServiceTest.java b/src/test/java/fr/xephi/authme/process/ProcessServiceTest.java index ab935d02..617e0d80 100644 --- a/src/test/java/fr/xephi/authme/process/ProcessServiceTest.java +++ b/src/test/java/fr/xephi/authme/process/ProcessServiceTest.java @@ -1,21 +1,20 @@ package fr.xephi.authme.process; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.permission.AuthGroupHandler; +import fr.xephi.authme.permission.AuthGroupType; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.ValidationService; import org.bukkit.command.CommandSender; -import org.junit.Before; +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.runners.MockitoJUnitRunner; @@ -31,31 +30,23 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class ProcessServiceTest { + @InjectMocks private ProcessService processService; + @Mock private ValidationService validationService; + @Mock private NewSetting settings; + @Mock private Messages messages; - @Mock - private PasswordSecurity passwordSecurity; - @Mock - private AuthMe authMe; - @Mock - private DataSource dataSource; - @Mock - private SpawnLoader spawnLoader; - @Mock - private PluginHooks pluginHooks; - @Mock - private BukkitService bukkitService; - @Before - public void setUpService() { - processService = new ProcessService(settings, messages, authMe, dataSource, passwordSecurity, - pluginHooks, spawnLoader, validationService, bukkitService); - } + @Mock + private PermissionsManager permissionsManager; + + @Mock + private AuthGroupHandler authGroupHandler; @Test public void shouldGetProperty() { @@ -136,73 +127,6 @@ public class ProcessServiceTest { verify(messages).retrieveSingle(key); } - @Test - public void shouldReturnAuthMeInstance() { - // given / when - AuthMe result = processService.getAuthMe(); - - // then - assertThat(result, equalTo(authMe)); - } - - @Test - public void shouldReturnPluginHooks() { - // given / when - PluginHooks result = processService.getPluginHooks(); - - // then - assertThat(result, equalTo(pluginHooks)); - } - - @Test - public void shouldReturnSpawnLoader() { - // given / when - SpawnLoader result = processService.getSpawnLoader(); - - // then - assertThat(result, equalTo(spawnLoader)); - } - - @Test - public void shouldReturnDatasource() { - // given / when - DataSource result = processService.getDataSource(); - - // then - assertThat(result, equalTo(dataSource)); - } - - @Test - public void shouldComputeHash() { - // given - String password = "test123"; - String username = "Username"; - HashedPassword hashedPassword = new HashedPassword("hashedResult", "salt12342"); - given(passwordSecurity.computeHash(password, username)).willReturn(hashedPassword); - - // when - HashedPassword result = processService.computeHash(password, username); - - // then - assertThat(result, equalTo(hashedPassword)); - verify(passwordSecurity).computeHash(password, username); - } - - @Test - public void shouldValidatePassword() { - // given - String user = "test-user"; - String password = "passw0rd"; - given(validationService.validatePassword(password, user)).willReturn(MessageKey.PASSWORD_MATCH_ERROR); - - // when - MessageKey result = processService.validatePassword(password, user); - - // then - assertThat(result, equalTo(MessageKey.PASSWORD_MATCH_ERROR)); - verify(validationService).validatePassword(password, user); - } - @Test public void shouldValidateEmail() { // given @@ -232,4 +156,34 @@ public class ProcessServiceTest { assertThat(result, equalTo(true)); verify(validationService).isEmailFreeForRegistration(email, sender); } + + @Test + public void shouldCheckPermission() { + // given + Player player = mock(Player.class); + PermissionNode permission = PlayerPermission.CHANGE_PASSWORD; + given(permissionsManager.hasPermission(player, permission)).willReturn(true); + + // when + boolean result = processService.hasPermission(player, permission); + + // then + verify(permissionsManager).hasPermission(player, permission); + assertThat(result, equalTo(true)); + } + + @Test + public void shouldSetPermissionGroup() { + // given + Player player = mock(Player.class); + AuthGroupType type = AuthGroupType.LOGGED_IN; + given(authGroupHandler.setGroup(player, type)).willReturn(true); + + // when + boolean result = processService.setGroup(player, type); + + // then + verify(authGroupHandler).setGroup(player, type); + assertThat(result, equalTo(true)); + } } diff --git a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java index 9f92a5c8..4d013bd3 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncAddEmailTest.java @@ -11,6 +11,7 @@ import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -26,12 +27,18 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class AsyncAddEmailTest { + @InjectMocks + private AsyncAddEmail asyncAddEmail; + @Mock private Player player; + @Mock private DataSource dataSource; + @Mock private PlayerCache playerCache; + @Mock private ProcessService service; @@ -44,7 +51,6 @@ public class AsyncAddEmailTest { public void shouldAddEmail() { // given String email = "my.mail@example.org"; - AsyncAddEmail process = createProcess(email); given(player.getName()).willReturn("testEr"); given(playerCache.isAuthenticated("tester")).willReturn(true); PlayerAuth auth = mock(PlayerAuth.class); @@ -55,7 +61,7 @@ public class AsyncAddEmailTest { given(service.isEmailFreeForRegistration(email, player)).willReturn(true); // when - process.run(); + asyncAddEmail.addEmail(player, email); // then verify(dataSource).updateEmail(auth); @@ -68,7 +74,6 @@ public class AsyncAddEmailTest { public void shouldReturnErrorWhenMailCannotBeSaved() { // given String email = "my.mail@example.org"; - AsyncAddEmail process = createProcess(email); given(player.getName()).willReturn("testEr"); given(playerCache.isAuthenticated("tester")).willReturn(true); PlayerAuth auth = mock(PlayerAuth.class); @@ -80,7 +85,7 @@ public class AsyncAddEmailTest { given(service.isEmailFreeForRegistration(email, player)).willReturn(true); // when - process.run(); + asyncAddEmail.addEmail(player, email); // then verify(dataSource).updateEmail(auth); @@ -90,7 +95,6 @@ public class AsyncAddEmailTest { @Test public void shouldNotAddMailIfPlayerAlreadyHasEmail() { // given - AsyncAddEmail process = createProcess("some.mail@example.org"); given(player.getName()).willReturn("my_Player"); given(playerCache.isAuthenticated("my_player")).willReturn(true); PlayerAuth auth = mock(PlayerAuth.class); @@ -98,7 +102,7 @@ public class AsyncAddEmailTest { given(playerCache.getAuth("my_player")).willReturn(auth); // when - process.run(); + asyncAddEmail.addEmail(player, "some.mail@example.org"); // then verify(service).send(player, MessageKey.USAGE_CHANGE_EMAIL); @@ -109,7 +113,6 @@ public class AsyncAddEmailTest { public void shouldNotAddMailIfItIsInvalid() { // given String email = "invalid_mail"; - AsyncAddEmail process = createProcess(email); given(player.getName()).willReturn("my_Player"); given(playerCache.isAuthenticated("my_player")).willReturn(true); PlayerAuth auth = mock(PlayerAuth.class); @@ -118,7 +121,7 @@ public class AsyncAddEmailTest { given(service.validateEmail(email)).willReturn(false); // when - process.run(); + asyncAddEmail.addEmail(player, email); // then verify(service).send(player, MessageKey.INVALID_EMAIL); @@ -129,7 +132,6 @@ public class AsyncAddEmailTest { public void shouldNotAddMailIfAlreadyUsed() { // given String email = "player@mail.tld"; - AsyncAddEmail process = createProcess(email); given(player.getName()).willReturn("TestName"); given(playerCache.isAuthenticated("testname")).willReturn(true); PlayerAuth auth = mock(PlayerAuth.class); @@ -139,7 +141,7 @@ public class AsyncAddEmailTest { given(service.isEmailFreeForRegistration(email, player)).willReturn(false); // when - process.run(); + asyncAddEmail.addEmail(player, email); // then verify(service).send(player, MessageKey.EMAIL_ALREADY_USED_ERROR); @@ -149,13 +151,12 @@ public class AsyncAddEmailTest { @Test public void shouldShowLoginMessage() { // given - AsyncAddEmail process = createProcess("test@mail.com"); given(player.getName()).willReturn("Username12"); given(playerCache.isAuthenticated("username12")).willReturn(false); given(dataSource.isAuthAvailable("Username12")).willReturn(true); // when - process.run(); + asyncAddEmail.addEmail(player, "test@mail.com"); // then verify(service).send(player, MessageKey.LOGIN_MESSAGE); @@ -165,14 +166,13 @@ public class AsyncAddEmailTest { @Test public void shouldShowEmailRegisterMessage() { // given - AsyncAddEmail process = createProcess("test@mail.com"); given(player.getName()).willReturn("user"); given(playerCache.isAuthenticated("user")).willReturn(false); given(dataSource.isAuthAvailable("user")).willReturn(false); given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); // when - process.run(); + asyncAddEmail.addEmail(player, "test@mail.com"); // then verify(service).send(player, MessageKey.REGISTER_EMAIL_MESSAGE); @@ -182,28 +182,17 @@ public class AsyncAddEmailTest { @Test public void shouldShowRegularRegisterMessage() { // given - AsyncAddEmail process = createProcess("test@mail.com"); given(player.getName()).willReturn("user"); given(playerCache.isAuthenticated("user")).willReturn(false); given(dataSource.isAuthAvailable("user")).willReturn(false); given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(false); // when - process.run(); + asyncAddEmail.addEmail(player, "test@mail.com"); // then verify(service).send(player, MessageKey.REGISTER_MESSAGE); verify(playerCache, never()).updatePlayer(any(PlayerAuth.class)); } - /** - * Create an instance of {@link AsyncAddEmail} with the class' mocks. - * - * @param email The email to use - * @return The created process - */ - private AsyncAddEmail createProcess(String email) { - return new AsyncAddEmail(player, email, dataSource, playerCache, service); - } - } diff --git a/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java b/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java index a90ff9b2..7b9dbfdb 100644 --- a/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java +++ b/src/test/java/fr/xephi/authme/process/email/AsyncChangeEmailTest.java @@ -5,11 +5,11 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.process.ProcessService; -import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; 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.runners.MockitoJUnitRunner; @@ -26,12 +26,18 @@ import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class AsyncChangeEmailTest { + @InjectMocks + private AsyncChangeEmail process; + @Mock private Player player; + @Mock private PlayerCache playerCache; + @Mock private DataSource dataSource; + @Mock private ProcessService service; @@ -39,7 +45,6 @@ public class AsyncChangeEmailTest { public void shouldAddEmail() { // given String newEmail = "new@mail.tld"; - AsyncChangeEmail process = createProcess("old@mail.tld", newEmail); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(true); PlayerAuth auth = authWithMail("old@mail.tld"); @@ -49,7 +54,7 @@ public class AsyncChangeEmailTest { given(service.isEmailFreeForRegistration(newEmail, player)).willReturn(true); // when - process.run(); + process.changeEmail(player, "old@mail.tld", newEmail); // then verify(dataSource).updateEmail(auth); @@ -61,7 +66,6 @@ public class AsyncChangeEmailTest { public void shouldShowErrorIfSaveFails() { // given String newEmail = "new@mail.tld"; - AsyncChangeEmail process = createProcess("old@mail.tld", newEmail); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(true); PlayerAuth auth = authWithMail("old@mail.tld"); @@ -71,7 +75,7 @@ public class AsyncChangeEmailTest { given(service.isEmailFreeForRegistration(newEmail, player)).willReturn(true); // when - process.run(); + process.changeEmail(player, "old@mail.tld", newEmail); // then verify(dataSource).updateEmail(auth); @@ -82,14 +86,13 @@ public class AsyncChangeEmailTest { @Test public void shouldShowAddEmailUsage() { // given - AsyncChangeEmail process = createProcess("old@mail.tld", "new@mail.tld"); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(true); PlayerAuth auth = authWithMail(null); given(playerCache.getAuth("bobby")).willReturn(auth); // when - process.run(); + process.changeEmail(player, "old@mail.tld", "new@mailt.tld"); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -101,7 +104,6 @@ public class AsyncChangeEmailTest { public void shouldRejectInvalidNewMail() { // given String newEmail = "bogus"; - AsyncChangeEmail process = createProcess("old@mail.tld", newEmail); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(true); PlayerAuth auth = authWithMail("old@mail.tld"); @@ -109,7 +111,7 @@ public class AsyncChangeEmailTest { given(service.validateEmail(newEmail)).willReturn(false); // when - process.run(); + process.changeEmail(player, "old@mail.tld", newEmail); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -121,7 +123,6 @@ public class AsyncChangeEmailTest { public void shouldRejectInvalidOldEmail() { // given String newEmail = "new@mail.tld"; - AsyncChangeEmail process = createProcess("old@mail.tld", newEmail); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(true); PlayerAuth auth = authWithMail("other@address.email"); @@ -131,7 +132,7 @@ public class AsyncChangeEmailTest { // when - process.run(); + process.changeEmail(player, "old@mail.tld", newEmail); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -143,7 +144,6 @@ public class AsyncChangeEmailTest { public void shouldRejectAlreadyUsedEmail() { // given String newEmail = "new@example.com"; - AsyncChangeEmail process = createProcess("old@example.com", newEmail); given(player.getName()).willReturn("Username"); given(playerCache.isAuthenticated("username")).willReturn(true); PlayerAuth auth = authWithMail("old@example.com"); @@ -152,7 +152,7 @@ public class AsyncChangeEmailTest { given(service.isEmailFreeForRegistration(newEmail, player)).willReturn(false); // when - process.run(); + process.changeEmail(player, "old@example.com", newEmail); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -163,13 +163,12 @@ public class AsyncChangeEmailTest { @Test public void shouldSendLoginMessage() { // given - AsyncChangeEmail process = createProcess("old@mail.tld", "new@mail.tld"); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(false); given(dataSource.isAuthAvailable("Bobby")).willReturn(true); // when - process.run(); + process.changeEmail(player, "old@mail.tld", "new@mail.tld"); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -180,14 +179,13 @@ public class AsyncChangeEmailTest { @Test public void shouldShowEmailRegistrationMessage() { // given - AsyncChangeEmail process = createProcess("old@mail.tld", "new@mail.tld"); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(false); given(dataSource.isAuthAvailable("Bobby")).willReturn(false); given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); // when - process.run(); + process.changeEmail(player, "old@mail.tld", "new@mail.tld"); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -198,14 +196,13 @@ public class AsyncChangeEmailTest { @Test public void shouldShowRegistrationMessage() { // given - AsyncChangeEmail process = createProcess("old@mail.tld", "new@mail.tld"); given(player.getName()).willReturn("Bobby"); given(playerCache.isAuthenticated("bobby")).willReturn(false); given(dataSource.isAuthAvailable("Bobby")).willReturn(false); given(service.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(false); // when - process.run(); + process.changeEmail(player, "old@mail.tld", "new@mail.tld"); // then verify(dataSource, never()).updateEmail(any(PlayerAuth.class)); @@ -219,8 +216,4 @@ public class AsyncChangeEmailTest { return auth; } - private AsyncChangeEmail createProcess(String oldEmail, String newEmail) { - given(service.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(5); - return new AsyncChangeEmail(player, oldEmail, newEmail, dataSource, playerCache, service); - } } diff --git a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java index 976a7a44..79745a33 100644 --- a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.security; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.NewSetting; @@ -13,8 +14,6 @@ import java.util.HashSet; import java.util.Set; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; @@ -25,13 +24,15 @@ import static org.mockito.Mockito.mock; */ public class HashAlgorithmIntegrationTest { - private static NewSetting settings; + private static AuthMeServiceInitializer initializer; @BeforeClass - public static void setUpWrapper() { - settings = mock(NewSetting.class); + public static void setUpConfigAndInjector() { + NewSetting settings = mock(NewSetting.class); given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(16); + initializer = new AuthMeServiceInitializer(); + initializer.register(NewSetting.class, settings); } @Test @@ -51,13 +52,11 @@ public class HashAlgorithmIntegrationTest { } @Test - public void shouldBeAbleToInstantiateEncryptionAlgorithms() throws InstantiationException, IllegalAccessException { + public void shouldBeAbleToInstantiateEncryptionAlgorithms() { // given / when / then for (HashAlgorithm algorithm : HashAlgorithm.values()) { if (!HashAlgorithm.CUSTOM.equals(algorithm) && !HashAlgorithm.PLAINTEXT.equals(algorithm)) { - EncryptionMethod method = PasswordSecurity.initializeEncryptionMethod(algorithm, settings); - assertThat("Encryption method for algorithm '" + algorithm + "' is not null", - method, not(nullValue())); + EncryptionMethod method = initializer.newInstance(algorithm.getClazz()); HashedPassword hashedPassword = method.computeHash("pwd", "name"); assertThat("Salt should not be null if method.hasSeparateSalt(), and vice versa. Method: '" + method + "'", StringUtils.isEmpty(hashedPassword.getSalt()), equalTo(!method.hasSeparateSalt())); diff --git a/src/test/java/fr/xephi/authme/security/HashUtilsTest.java b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java index e515fdbb..c7ef1ccd 100644 --- a/src/test/java/fr/xephi/authme/security/HashUtilsTest.java +++ b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java @@ -18,7 +18,7 @@ public class HashUtilsTest { /** * List of passwords whose hash is provided to the class to test against. */ - public static final String[] GIVEN_PASSWORDS = {"", "password", "PassWord1", "&^%te$t?Pw@_"}; + private static final String[] GIVEN_PASSWORDS = {"", "password", "PassWord1", "&^%te$t?Pw@_"}; @Test public void shouldHashMd5() { diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index 470b8a2d..e98f2377 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -4,6 +4,7 @@ import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.JOOMLA; @@ -30,7 +31,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -41,12 +41,20 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class PasswordSecurityTest { + private AuthMeServiceInitializer initializer; + + @Mock + private NewSetting settings; + @Mock private PluginManager pluginManager; + @Mock private DataSource dataSource; + @Mock private EncryptionMethod method; + private Class caughtClassInEvent; @BeforeClass @@ -65,12 +73,16 @@ public class PasswordSecurityTest { Object[] arguments = invocation.getArguments(); if (arguments[0] instanceof PasswordEncryptionEvent) { PasswordEncryptionEvent event = (PasswordEncryptionEvent) arguments[0]; - caughtClassInEvent = event.getMethod() != null ? event.getMethod().getClass() : null; + caughtClassInEvent = event.getMethod() == null ? null : event.getMethod().getClass(); event.setMethod(method); } return null; } }).when(pluginManager).callEvent(any(Event.class)); + initializer = new AuthMeServiceInitializer(); + initializer.register(NewSetting.class, settings); + initializer.register(DataSource.class, dataSource); + initializer.register(PluginManager.class, pluginManager); } @Test @@ -84,8 +96,8 @@ public class PasswordSecurityTest { given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(true); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.BCRYPT, false), pluginManager); + initSettings(HashAlgorithm.BCRYPT, false); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when boolean result = security.comparePassword(clearTextPass, playerName); @@ -107,8 +119,8 @@ public class PasswordSecurityTest { given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.CUSTOM, false), pluginManager); + initSettings(HashAlgorithm.CUSTOM, false); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when boolean result = security.comparePassword(clearTextPass, playerName); @@ -127,8 +139,8 @@ public class PasswordSecurityTest { String clearTextPass = "tables"; given(dataSource.getPassword(playerName)).willReturn(null); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.MD5, false), pluginManager); + initSettings(HashAlgorithm.MD5, false); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when boolean result = security.comparePassword(clearTextPass, playerName); @@ -155,8 +167,8 @@ public class PasswordSecurityTest { given(dataSource.getPassword(argThat(equalToIgnoringCase(playerName)))).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); given(method.computeHash(clearTextPass, playerLowerCase)).willReturn(newPassword); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.MD5, true), pluginManager); + initSettings(HashAlgorithm.MD5, true); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when boolean result = security.comparePassword(clearTextPass, playerName); @@ -180,8 +192,8 @@ public class PasswordSecurityTest { String clearTextPass = "someInvalidPassword"; given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.MD5, true), pluginManager); + initSettings(HashAlgorithm.MD5, true); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when boolean result = security.comparePassword(clearTextPass, playerName); @@ -199,8 +211,8 @@ public class PasswordSecurityTest { String usernameLowerCase = username.toLowerCase(); HashedPassword hashedPassword = new HashedPassword("$T$est#Hash", "__someSalt__"); given(method.computeHash(password, usernameLowerCase)).willReturn(hashedPassword); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.JOOMLA, true), pluginManager); + initSettings(HashAlgorithm.JOOMLA, true); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when HashedPassword result = security.computeHash(password, username); @@ -222,8 +234,8 @@ public class PasswordSecurityTest { HashedPassword hashedPassword = new HashedPassword("~T!est#Hash"); given(method.computeHash(password, username)).willReturn(hashedPassword); given(method.hasSeparateSalt()).willReturn(true); - PasswordSecurity security = - new PasswordSecurity(dataSource, mockSettings(HashAlgorithm.XAUTH, false), pluginManager); + initSettings(HashAlgorithm.XAUTH, false); + PasswordSecurity security = initializer.newInstance(PasswordSecurity.class); // when boolean result = security.comparePassword(password, hashedPassword, username); @@ -238,8 +250,8 @@ public class PasswordSecurityTest { @Test public void shouldReloadSettings() { // given - NewSetting settings = mockSettings(HashAlgorithm.BCRYPT, false); - PasswordSecurity passwordSecurity = new PasswordSecurity(dataSource, settings, pluginManager); + initSettings(HashAlgorithm.BCRYPT, false); + PasswordSecurity passwordSecurity = initializer.newInstance(PasswordSecurity.class); given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.MD5); given(settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)).willReturn(true); @@ -253,13 +265,11 @@ public class PasswordSecurityTest { equalTo((Object) Boolean.TRUE)); } - private static NewSetting mockSettings(HashAlgorithm algorithm, boolean supportOldPassword) { - NewSetting settings = mock(NewSetting.class); + private void initSettings(HashAlgorithm algorithm, boolean supportOldPassword) { given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(algorithm); given(settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)).willReturn(supportOldPassword); given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(16); - return settings; } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index 73e52fa3..1ee72840 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -12,6 +12,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeThat; /** * Test for implementations of {@link EncryptionMethod}. @@ -36,6 +37,14 @@ public abstract class AbstractEncryptionMethodTest { "asdfg:hjkl", "::test", "~#$#~~~#$#~", "d41d8cd98f00b204e9800998ecf427e", "$2y$7a$da641e404b982ed" }; + /** + * Certain hash algorithms are slow by design, which has a considerable effect on these unit tests. + * Setting the property below to "true" will reduce the checks in these unit tests as to offer fast, + * partial tests during development. + */ + private static final boolean SKIP_LONG_TESTS = + "true".equals(System.getProperty("project.skipExtendedHashTests")); + /** The encryption method to test. */ private EncryptionMethod method; /** Map with the hashes against which the entries in GIVEN_PASSWORDS are tested. */ @@ -79,8 +88,10 @@ public abstract class AbstractEncryptionMethodTest { @Test public void testGivenPasswords() { - // Test all entries in GIVEN_PASSWORDS except the last one - for (int i = 0; i < GIVEN_PASSWORDS.length - 1; ++i) { + // Start with the 2nd to last password if we skip long tests + int start = SKIP_LONG_TESTS ? GIVEN_PASSWORDS.length - 2 : 0; + // Test entries in GIVEN_PASSWORDS except the last one + for (int i = start; i < GIVEN_PASSWORDS.length - 1; ++i) { String password = GIVEN_PASSWORDS[i]; assertTrue("Hash for password '" + password + "' should match", doesGivenHashMatch(password, method)); @@ -124,12 +135,14 @@ public abstract class AbstractEncryptionMethodTest { assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", method.comparePassword(password.toUpperCase(), hashedPassword, USERNAME)); } + assumeThat(SKIP_LONG_TESTS, equalTo(false)); } } /** Tests various strings to ensure that encryption methods don't rely on the hash's format too much. */ @Test public void testMalformedHashes() { + assumeThat(SKIP_LONG_TESTS, equalTo(false)); String salt = method.hasSeparateSalt() ? "testSalt" : null; for (String bogusHash : BOGUS_HASHES) { HashedPassword hashedPwd = new HashedPassword(bogusHash, salt); @@ -152,7 +165,7 @@ public abstract class AbstractEncryptionMethodTest { * * @param method The method to create a test class for */ - static void generateTest(EncryptionMethod method) { + protected static void generateTest(EncryptionMethod method) { String className = method.getClass().getSimpleName(); // Create javadoc and "public class extends" and the constructor call "super(new Class()," System.out.println("/**\n * Test for {@link " + className + "}.\n */"); diff --git a/src/test/java/fr/xephi/authme/settings/ConfigFileConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/ConfigFileConsistencyTest.java index 6a7d7fe1..1ce7ec41 100644 --- a/src/test/java/fr/xephi/authme/settings/ConfigFileConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/ConfigFileConsistencyTest.java @@ -12,13 +12,14 @@ import org.junit.Test; import java.io.File; import java.io.IOException; -import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** @@ -56,9 +57,8 @@ public class ConfigFileConsistencyTest { @Test public void shouldNotHaveUnknownConfigs() { // given - URL url = this.getClass().getResource(CONFIG_FILE); - File configFile = new File(url.getFile()); - YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile); + File configFile = TestHelper.getJarFile(CONFIG_FILE); + FileConfiguration configuration = YamlConfiguration.loadConfiguration(configFile); Map allReadProperties = configuration.getValues(true); Set knownKeys = getAllKnownPropertyPaths(); @@ -78,6 +78,20 @@ public class ConfigFileConsistencyTest { } } + @Test + public void shouldHaveValueCorrespondingToPropertyDefault() { + // given + File configFile = TestHelper.getJarFile(CONFIG_FILE); + FileConfiguration configuration = YamlConfiguration.loadConfiguration(configFile); + PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields(); + + // when / then + for (Property property : propertyMap.keySet()) { + assertThat("Default value of '" + property.getPath() + "' in config.yml should be the same as in Property", + property.getFromFile(configuration).equals(property.getDefaultValue()), equalTo(true)); + } + } + private static Set getAllKnownPropertyPaths() { PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields(); Set paths = new HashSet<>(propertyMap.size()); diff --git a/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java b/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java index 8bd9da4b..06088e19 100644 --- a/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java +++ b/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java @@ -2,6 +2,7 @@ package fr.xephi.authme.settings; import com.google.common.io.Files; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.Location; @@ -48,7 +49,8 @@ public class SpawnLoaderTest { @Test public void shouldSetSpawn() { // given - SpawnLoader spawnLoader = new SpawnLoader(testFolder, settings, mock(PluginHooks.class)); + SpawnLoader spawnLoader = + new SpawnLoader(testFolder, settings, mock(PluginHooks.class), mock(DataSource.class)); World world = mock(World.class); given(world.getName()).willReturn("new_world"); Location newSpawn = new Location(world, 123, 45.0, -67.89); diff --git a/src/test/java/fr/xephi/authme/settings/domain/PropertyTest.java b/src/test/java/fr/xephi/authme/settings/domain/PropertyTest.java index ff84c393..3ea8ff0e 100644 --- a/src/test/java/fr/xephi/authme/settings/domain/PropertyTest.java +++ b/src/test/java/fr/xephi/authme/settings/domain/PropertyTest.java @@ -38,6 +38,9 @@ public class PropertyTest { when(configuration.isList("list.path.test")).thenReturn(true); when(configuration.getStringList("list.path.test")).thenReturn(Arrays.asList("test1", "Test2", "3rd test")); when(configuration.isList("list.path.wrong")).thenReturn(false); + when(configuration.isList("lowercaselist.path.test")).thenReturn(true); + when(configuration.getStringList("lowercaselist.path.test")).thenReturn(Arrays.asList("test1", "Test2", "3rd test")); + when(configuration.isList("lowercaselist.path.wrong")).thenReturn(false); } /* Boolean */ @@ -141,4 +144,29 @@ public class PropertyTest { assertThat(result, contains("default", "list", "elements")); } + /* Lowercase String list */ + @Test + public void shouldGetLowercaseStringListValue() { + // given + Property> property = Property.newLowercaseListProperty("lowercaselist.path.test", "1", "b"); + + // when + List result = property.getFromFile(configuration); + + // then + assertThat(result, contains("test1", "test2", "3rd test")); + } + + @Test + public void shouldGetLowercaseStringListDefault() { + // given + Property> property = + Property.newLowercaseListProperty("lowercaselist.path.wrong", "default", "list", "elements"); + + // when + List result = property.getFromFile(configuration); + + // then + assertThat(result, contains("default", "list", "elements")); + } } diff --git a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java new file mode 100644 index 00000000..51e390c4 --- /dev/null +++ b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java @@ -0,0 +1,217 @@ +package fr.xephi.authme.task; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.BukkitService; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link LimboPlayerTaskManager}. + */ +@RunWith(MockitoJUnitRunner.class) +public class LimboPlayerTaskManagerTest { + + @InjectMocks + private LimboPlayerTaskManager limboPlayerTaskManager; + + @Mock + private Messages messages; + + @Mock + private NewSetting settings; + + @Mock + private BukkitService bukkitService; + + @Mock + private LimboCache limboCache; + + @Mock + private PlayerCache playerCache; + + @BeforeClass + public static void setupLogger() { + TestHelper.setupLogger(); + } + + @Test + public void shouldRegisterMessageTask() { + // given + String name = "bobby"; + LimboPlayer limboPlayer = mock(LimboPlayer.class); + given(limboCache.getLimboPlayer(name)).willReturn(limboPlayer); + MessageKey key = MessageKey.REGISTER_EMAIL_MESSAGE; + given(messages.retrieve(key)).willReturn(new String[]{"Please register!"}); + BukkitTask bukkiTask = mock(BukkitTask.class); + given(bukkitService.runTask(any(MessageTask.class))).willReturn(bukkiTask); + given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(12); + given(settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); + + // when + limboPlayerTaskManager.registerMessageTask(name, false); + + // then + verify(limboPlayer).setMessageTask(bukkiTask); + verify(messages).retrieve(key); + } + + @Test + public void shouldNotScheduleTaskForMissingLimboPlayer() { + // given + String name = "ghost"; + given(limboCache.getLimboPlayer(name)).willReturn(null); + given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(5); + + // when + limboPlayerTaskManager.registerMessageTask(name, true); + + // then + verify(limboCache).getLimboPlayer(name); + verifyZeroInteractions(bukkitService); + verifyZeroInteractions(messages); + } + + @Test + public void shouldNotScheduleTaskForZeroAsInterval() { + // given + String name = "Tester1"; + LimboPlayer limboPlayer = mock(LimboPlayer.class); + given(limboCache.getLimboPlayer(name)).willReturn(limboPlayer); + BukkitTask bukkiTask = mock(BukkitTask.class); + given(bukkitService.runTask(any(MessageTask.class))).willReturn(bukkiTask); + given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(0); + + // when + limboPlayerTaskManager.registerMessageTask(name, true); + + // then + verifyZeroInteractions(limboPlayer, bukkitService); + } + + @Test + public void shouldCancelExistingMessageTask() { + // given + LimboPlayer limboPlayer = mock(LimboPlayer.class); + BukkitTask existingMessageTask = mock(BukkitTask.class); + given(limboPlayer.getMessageTask()).willReturn(existingMessageTask); + + String name = "bobby"; + given(limboCache.getLimboPlayer(name)).willReturn(limboPlayer); + given(messages.retrieve(MessageKey.REGISTER_EMAIL_MESSAGE)) + .willReturn(new String[]{"Please register", "Use /register"}); + + BukkitTask bukkiTask = mock(BukkitTask.class); + given(bukkitService.runTask(any(MessageTask.class))).willReturn(bukkiTask); + given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(8); + given(settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)).willReturn(true); + + // when + limboPlayerTaskManager.registerMessageTask(name, false); + + // then + verify(limboPlayer).setMessageTask(bukkiTask); + verify(messages).retrieve(MessageKey.REGISTER_EMAIL_MESSAGE); + verify(existingMessageTask).cancel(); + } + + @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.getLimboPlayer(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); + + // then + verify(limboPlayer).setTimeoutTask(bukkitTask); + verify(bukkitService).runTaskLater(any(TimeoutTask.class), eq(600L)); // 30 * TICKS_PER_SECOND + 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.getLimboPlayer(name)).willReturn(null); + given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(27); + + // when + limboPlayerTaskManager.registerTimeoutTask(player); + + // then + verifyZeroInteractions(bukkitService, messages); + } + + @Test + public void shouldNotRegisterTimeoutTaskForZeroTimeout() { + // given + String name = "snail"; + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + LimboPlayer limboPlayer = mock(LimboPlayer.class); + given(limboCache.getLimboPlayer(name)).willReturn(limboPlayer); + given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(0); + + // when + limboPlayerTaskManager.registerTimeoutTask(player); + + // then + verifyZeroInteractions(limboPlayer, bukkitService); + } + + @Test + public void shouldCancelExistingTimeoutTask() { + // given + String name = "l33tPlayer"; + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + LimboPlayer limboPlayer = mock(LimboPlayer.class); + BukkitTask existingTask = mock(BukkitTask.class); + given(limboPlayer.getTimeoutTask()).willReturn(existingTask); + given(limboCache.getLimboPlayer(name)).willReturn(limboPlayer); + 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); + + // then + verify(existingTask).cancel(); + verify(limboPlayer).setTimeoutTask(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/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java index 6cc7799a..a09ac87c 100644 --- a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.util; +import fr.xephi.authme.TestHelper; import org.junit.Test; import java.io.File; @@ -145,4 +146,9 @@ public class StringUtilsTest { // then assertThat(result, equalTo("path" + File.separator + "to" + File.separator + "test-file.txt")); } + + @Test + public void shouldHaveHiddenConstructor() { + TestHelper.validateHasOnlyPrivateEmptyConstructor(StringUtils.class); + } } diff --git a/src/test/java/fr/xephi/authme/util/TeleportationServiceTest.java b/src/test/java/fr/xephi/authme/util/TeleportationServiceTest.java new file mode 100644 index 00000000..ad9d9620 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/TeleportationServiceTest.java @@ -0,0 +1,422 @@ +package fr.xephi.authme.util; + +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.events.FirstSpawnTeleportEvent; +import fr.xephi.authme.events.SpawnTeleportEvent; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.util.Arrays; + +import static fr.xephi.authme.TestHelper.runSyncDelayedTask; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link TeleportationService}. + */ +@RunWith(MockitoJUnitRunner.class) +public class TeleportationServiceTest { + + @InjectMocks + private TeleportationService teleportationService; + + @Mock + private NewSetting settings; + + @Mock + private BukkitService bukkitService; + + @Mock + private SpawnLoader spawnLoader; + + @Mock + private PlayerCache playerCache; + + @Before + public void setUpForcedWorlds() { + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_ON_WORLDS)) + .willReturn(Arrays.asList("forced1", "OtherForced")); + teleportationService.reload(); + + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(false); + } + + // ----------- + // JOINING + // ----------- + @Test + public void shouldNotTeleportPlayerOnJoin() { + // given + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(true); + Player player = mock(Player.class); + + // when + teleportationService.teleportOnJoin(player); + + // then + verifyZeroInteractions(player); + verifyZeroInteractions(bukkitService); + } + + @Test + public void shouldTeleportPlayerToFirstSpawn() { + // given + Player player = mock(Player.class); + given(player.hasPlayedBefore()).willReturn(false); + given(player.isOnline()).willReturn(true); + Location firstSpawn = mockLocation(); + given(spawnLoader.getFirstSpawn()).willReturn(firstSpawn); + + // when + teleportationService.teleportOnJoin(player); + runSyncDelayedTask(bukkitService); + + // then + verify(player).teleport(firstSpawn); + verify(bukkitService).callEvent(any(FirstSpawnTeleportEvent.class)); + verify(spawnLoader).getFirstSpawn(); + verify(spawnLoader, never()).getSpawnLocation(any(Player.class)); + } + + @Test + public void shouldTeleportPlayerToSpawn() { + // given + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + Player player = mock(Player.class); + given(player.hasPlayedBefore()).willReturn(true); + given(player.isOnline()).willReturn(true); + Location spawn = mockLocation(); + given(spawnLoader.getSpawnLocation(player)).willReturn(spawn); + + // when + teleportationService.teleportOnJoin(player); + runSyncDelayedTask(bukkitService); + + // then + verify(player).teleport(spawn); + verify(bukkitService).callEvent(any(SpawnTeleportEvent.class)); + verify(spawnLoader).getSpawnLocation(player); + } + + @Test + // No first spawn defined, no teleport settings enabled + public void shouldNotTeleportNewPlayer() { + // given + Player player = mock(Player.class); + given(player.hasPlayedBefore()).willReturn(false); + given(player.isOnline()).willReturn(true); + given(player.getWorld()).willReturn(mock(World.class)); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(false); + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(false); + given(spawnLoader.getFirstSpawn()).willReturn(null); + + // when + teleportationService.teleportOnJoin(player); + + // then + verify(player, never()).teleport(any(Location.class)); + verify(spawnLoader).getFirstSpawn(); + verify(spawnLoader, never()).getSpawnLocation(any(Player.class)); + verifyZeroInteractions(bukkitService); + } + + @Test + public void shouldTeleportPlayerDueToForcedWorld() { + // given + Player player = mock(Player.class); + given(player.hasPlayedBefore()).willReturn(true); + given(player.isOnline()).willReturn(true); + + World playerWorld = mock(World.class); + given(playerWorld.getName()).willReturn("OtherForced"); + given(player.getWorld()).willReturn(playerWorld); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(false); + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(true); + + Location spawn = mockLocation(); + given(spawnLoader.getSpawnLocation(player)).willReturn(spawn); + + // when + teleportationService.teleportOnJoin(player); + runSyncDelayedTask(bukkitService); + + // then + verify(player).teleport(spawn); + verify(bukkitService).callEvent(any(SpawnTeleportEvent.class)); + verify(spawnLoader).getSpawnLocation(player); + } + + @Test + public void shouldNotTeleportPlayerForRemovedLocationInEvent() { + // given + final Player player = mock(Player.class); + given(player.hasPlayedBefore()).willReturn(true); + Location spawn = mockLocation(); + given(spawnLoader.getSpawnLocation(player)).willReturn(spawn); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + SpawnTeleportEvent event = (SpawnTeleportEvent) invocation.getArguments()[0]; + assertThat(event.getPlayer(), equalTo(player)); + event.setTo(null); + return null; + } + }).when(bukkitService).callEvent(any(SpawnTeleportEvent.class)); + + // when + teleportationService.teleportOnJoin(player); + runSyncDelayedTask(bukkitService); + + // then + verify(bukkitService).callEvent(any(SpawnTeleportEvent.class)); + verify(player, never()).teleport(any(Location.class)); + } + + @Test + public void shouldNotTeleportPlayerForCanceledEvent() { + // given + final Player player = mock(Player.class); + given(player.hasPlayedBefore()).willReturn(true); + Location spawn = mockLocation(); + given(spawnLoader.getSpawnLocation(player)).willReturn(spawn); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + SpawnTeleportEvent event = (SpawnTeleportEvent) invocation.getArguments()[0]; + assertThat(event.getPlayer(), equalTo(player)); + event.setCancelled(true); + return null; + } + }).when(bukkitService).callEvent(any(SpawnTeleportEvent.class)); + + // when + teleportationService.teleportOnJoin(player); + runSyncDelayedTask(bukkitService); + + // then + verify(bukkitService).callEvent(any(SpawnTeleportEvent.class)); + verify(player, never()).teleport(any(Location.class)); + } + + + // --------- + // LOGIN + // --------- + @Test + public void shouldNotTeleportUponLogin() { + // given + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(true); + Player player = mock(Player.class); + PlayerAuth auth = mock(PlayerAuth.class); + LimboPlayer limbo = mock(LimboPlayer.class); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + + // then + verifyZeroInteractions(player, auth, limbo, bukkitService, spawnLoader); + } + + @Test + public void shouldTeleportPlayerToSpawnAfterLogin() { + // given + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(true); + Player player = mock(Player.class); + given(player.isOnline()).willReturn(true); + Location spawn = mockLocation(); + given(spawnLoader.getSpawnLocation(player)).willReturn(spawn); + PlayerAuth auth = mock(PlayerAuth.class); + LimboPlayer limbo = mock(LimboPlayer.class); + Location limboLocation = mockLocation(); + given(limboLocation.getWorld().getName()).willReturn("forced1"); + given(limbo.getLoc()).willReturn(limboLocation); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + runSyncDelayedTask(bukkitService); + + // then + verify(player).teleport(spawn); + } + + @Test + // Check that the worlds for "force spawn loc after login" are case-sensitive + public void shouldNotTeleportToSpawnForOtherCaseInWorld() { + // given + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(true); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(false); + Player player = mock(Player.class); + given(player.isOnline()).willReturn(true); + Location spawn = mockLocation(); + given(spawnLoader.getSpawnLocation(player)).willReturn(spawn); + PlayerAuth auth = mock(PlayerAuth.class); + LimboPlayer limbo = mock(LimboPlayer.class); + Location limboLocation = mockLocation(); + given(limboLocation.getWorld().getName()).willReturn("Forced1"); // different case + given(limbo.getLoc()).willReturn(limboLocation); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + + // then + verify(player, never()).teleport(spawn); + verifyZeroInteractions(bukkitService, spawnLoader); + } + + @Test + public void shouldTeleportBackToPlayerAuthLocation() { + // given + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(false); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + given(settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)).willReturn(true); + + PlayerAuth auth = createAuthWithLocation(); + auth.setWorld("myWorld"); + World world = mock(World.class); + given(bukkitService.getWorld("myWorld")).willReturn(world); + + Player player = mock(Player.class); + given(player.isOnline()).willReturn(true); + LimboPlayer limbo = mock(LimboPlayer.class); + Location limboLocation = mockLocation(); + given(limbo.getLoc()).willReturn(limboLocation); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + runSyncDelayedTask(bukkitService); + + // then + ArgumentCaptor locationCaptor = ArgumentCaptor.forClass(Location.class); + verify(player).teleport(locationCaptor.capture()); + assertCorrectLocation(locationCaptor.getValue(), auth, world); + } + + @Test + public void shouldTeleportAccordingToPlayerAuthAndPlayerWorldAsFallback() { + // given + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(false); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + given(settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)).willReturn(true); + + PlayerAuth auth = createAuthWithLocation(); + auth.setWorld("myWorld"); + given(bukkitService.getWorld("myWorld")).willReturn(null); + + Player player = mock(Player.class); + given(player.isOnline()).willReturn(true); + World world = mock(World.class); + given(player.getWorld()).willReturn(world); + LimboPlayer limbo = mock(LimboPlayer.class); + Location limboLocation = mockLocation(); + given(limbo.getLoc()).willReturn(limboLocation); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + runSyncDelayedTask(bukkitService); + + // then + ArgumentCaptor locationCaptor = ArgumentCaptor.forClass(Location.class); + verify(player).teleport(locationCaptor.capture()); + assertCorrectLocation(locationCaptor.getValue(), auth, world); + } + + @Test + public void shouldTeleportWithLimboPlayerIfAuthYCoordIsNotSet() { + // given + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(false); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + given(settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)).willReturn(true); + + PlayerAuth auth = createAuthWithLocation(); + auth.setQuitLocY(0.0); + auth.setWorld("authWorld"); + Player player = mock(Player.class); + given(player.isOnline()).willReturn(true); + World world = mock(World.class); + given(player.getWorld()).willReturn(world); + LimboPlayer limbo = mock(LimboPlayer.class); + Location location = mockLocation(); + given(limbo.getLoc()).willReturn(location); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + runSyncDelayedTask(bukkitService); + + // then + verify(player).teleport(location); + verify(bukkitService, never()).getWorld(anyString()); + } + + @Test + public void shouldTeleportWithLimboPlayerIfSaveQuitLocIsDisabled() { + // given + given(settings.getProperty(RestrictionSettings.FORCE_SPAWN_LOCATION_AFTER_LOGIN)).willReturn(false); + given(settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)).willReturn(true); + given(settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION)).willReturn(false); + + PlayerAuth auth = createAuthWithLocation(); + Player player = mock(Player.class); + given(player.isOnline()).willReturn(true); + World world = mock(World.class); + given(player.getWorld()).willReturn(world); + LimboPlayer limbo = mock(LimboPlayer.class); + Location location = mockLocation(); + given(limbo.getLoc()).willReturn(location); + + // when + teleportationService.teleportOnLogin(player, auth, limbo); + runSyncDelayedTask(bukkitService); + + // then + verify(player).teleport(location); + } + + + // We check that the World in Location is set, this method creates a mock World in Location for us + private static Location mockLocation() { + Location location = mock(Location.class); + given(location.getWorld()).willReturn(mock(World.class)); + return location; + } + + private static PlayerAuth createAuthWithLocation() { + return PlayerAuth.builder() + .name("bobby") + .locX(123.45).locY(23.4).locZ(-4.567) + .build(); + } + + private void assertCorrectLocation(Location location, PlayerAuth auth, World world) { + assertThat(location.getX(), equalTo(auth.getQuitLocX())); + assertThat(location.getY(), equalTo(auth.getQuitLocY())); + assertThat(location.getZ(), equalTo(auth.getQuitLocZ())); + assertThat(location.getWorld(), equalTo(world)); + } + +} diff --git a/src/test/java/fr/xephi/authme/util/ValidationServiceTest.java b/src/test/java/fr/xephi/authme/util/ValidationServiceTest.java index ed784510..0d52f775 100644 --- a/src/test/java/fr/xephi/authme/util/ValidationServiceTest.java +++ b/src/test/java/fr/xephi/authme/util/ValidationServiceTest.java @@ -9,6 +9,7 @@ import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; import org.junit.Before; import org.junit.Test; @@ -20,7 +21,6 @@ import java.util.Arrays; import java.util.Collections; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -53,56 +53,56 @@ public class ValidationServiceTest { @Test public void shouldRejectPasswordSameAsUsername() { // given/when - MessageKey error = validationService.validatePassword("bobby", "Bobby"); + ValidationResult error = validationService.validatePassword("bobby", "Bobby"); // then - assertThat(error, equalTo(MessageKey.PASSWORD_IS_USERNAME_ERROR)); + assertErrorEquals(error, MessageKey.PASSWORD_IS_USERNAME_ERROR); } @Test public void shouldRejectPasswordNotMatchingPattern() { // given/when // service mock returns pattern a-zA-Z -> numbers should not be accepted - MessageKey error = validationService.validatePassword("invalid1234", "myPlayer"); + ValidationResult error = validationService.validatePassword("invalid1234", "myPlayer"); // then - assertThat(error, equalTo(MessageKey.PASSWORD_MATCH_ERROR)); + assertErrorEquals(error, MessageKey.PASSWORD_CHARACTERS_ERROR, "[a-zA-Z]+"); } @Test public void shouldRejectTooShortPassword() { // given/when - MessageKey error = validationService.validatePassword("ab", "tester"); + ValidationResult error = validationService.validatePassword("ab", "tester"); // then - assertThat(error, equalTo(MessageKey.INVALID_PASSWORD_LENGTH)); + assertErrorEquals(error, MessageKey.INVALID_PASSWORD_LENGTH); } @Test public void shouldRejectTooLongPassword() { // given/when - MessageKey error = validationService.validatePassword(Strings.repeat("a", 30), "player"); + ValidationResult error = validationService.validatePassword(Strings.repeat("a", 30), "player"); // then - assertThat(error, equalTo(MessageKey.INVALID_PASSWORD_LENGTH)); + assertErrorEquals(error, MessageKey.INVALID_PASSWORD_LENGTH); } @Test public void shouldRejectUnsafePassword() { // given/when - MessageKey error = validationService.validatePassword("unsafe", "playertest"); + ValidationResult error = validationService.validatePassword("unsafe", "playertest"); // then - assertThat(error, equalTo(MessageKey.PASSWORD_UNSAFE_ERROR)); + assertErrorEquals(error, MessageKey.PASSWORD_UNSAFE_ERROR); } @Test public void shouldAcceptValidPassword() { // given/when - MessageKey error = validationService.validatePassword("safePass", "some_user"); + ValidationResult error = validationService.validatePassword("safePass", "some_user"); // then - assertThat(error, nullValue()); + assertThat(error.hasError(), equalTo(false)); } @Test @@ -230,4 +230,10 @@ public class ValidationServiceTest { // then assertThat(result, equalTo(true)); } + + private static void assertErrorEquals(ValidationResult validationResult, MessageKey messageKey, String... args) { + assertThat(validationResult.hasError(), equalTo(true)); + assertThat(validationResult.getMessageKey(), equalTo(messageKey)); + assertThat(validationResult.getArgs(), equalTo(args)); + } } diff --git a/src/test/java/tools/README.md b/src/test/java/tools/README.md new file mode 100644 index 00000000..012405cf --- /dev/null +++ b/src/test/java/tools/README.md @@ -0,0 +1,6 @@ +# About the _tools_ Folder + +This _tools_ folder provides helpers and extended tests useful during the development of AuthMe. +This folder is not included during the build of AuthMe and does not contain unit tests. + +Run the `ToolsRunner` class to perform a task. diff --git a/src/tools/ToolsRunner.java b/src/test/java/tools/ToolsRunner.java similarity index 74% rename from src/tools/ToolsRunner.java rename to src/test/java/tools/ToolsRunner.java index d920b23f..b387f32f 100644 --- a/src/tools/ToolsRunner.java +++ b/src/test/java/tools/ToolsRunner.java @@ -1,5 +1,8 @@ -import utils.ToolTask; -import utils.ToolsConstants; +package tools; + +import tools.utils.AutoToolTask; +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; import java.io.File; import java.lang.reflect.Constructor; @@ -27,14 +30,25 @@ public final class ToolsRunner { File toolsFolder = new File(ToolsConstants.TOOLS_SOURCE_ROOT); Map tasks = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); collectTasksInDirectory(toolsFolder, tasks); - listAllTasks(tasks); - // Prompt user for task and handle input + if (args == null || args.length == 0) { + promptAndExecuteTask(tasks); + } else { + executeAutomaticTasks(tasks, args); + } + } + + private static void promptAndExecuteTask(Map tasks) { + System.out.println("The following tasks are available:"); + for (String key : tasks.keySet()) { + System.out.println("- " + key); + } + System.out.println("Please enter the task to run:"); Scanner scanner = new Scanner(System.in); String inputTask = scanner.nextLine(); - ToolTask task = tasks.get(inputTask); + ToolTask task = tasks.get(inputTask); if (task != null) { task.execute(scanner); } else { @@ -43,10 +57,16 @@ public final class ToolsRunner { scanner.close(); } - private static void listAllTasks(Map taskCollection) { - System.out.println("The following tasks are available:"); - for (String key : taskCollection.keySet()) { - System.out.println("- " + key); + private static void executeAutomaticTasks(Map tasks, String... requests) { + for (String taskName : requests) { + ToolTask task = tasks.get(taskName); + if (task == null) { + System.out.format("Unknown task '%s'%n", taskName); + } else if (task instanceof AutoToolTask) { + ((AutoToolTask) task).executeDefault(); + } else { + System.out.format("Task '%s' cannot be run on command line%n", taskName); + } } } @@ -61,7 +81,7 @@ public final class ToolsRunner { private static void collectTasksInDirectory(File dir, Map taskCollection) { File[] files = dir.listFiles(); if (files == null) { - throw new RuntimeException("Cannot read folder '" + dir + "'"); + throw new IllegalStateException("Cannot read folder '" + dir + "'"); } for (File file : files) { if (file.isDirectory()) { @@ -92,7 +112,7 @@ public final class ToolsRunner { return constructor.newInstance(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { - throw new RuntimeException("Cannot instantiate task '" + taskClass + "'"); + throw new IllegalStateException("Cannot instantiate task '" + taskClass + "'"); } } @@ -108,16 +128,16 @@ public final class ToolsRunner { } String filePath = file.getPath(); - String className = filePath + String className = "tools." + filePath .substring(ToolsConstants.TOOLS_SOURCE_ROOT.length(), filePath.length() - 5) .replace(File.separator, "."); try { - Class clazz = ClassLoader.getSystemClassLoader().loadClass(className); + Class clazz = ToolsRunner.class.getClassLoader().loadClass(className); return ToolTask.class.isAssignableFrom(clazz) && isInstantiable(clazz) ? (Class) clazz : null; } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + throw new IllegalStateException(e); } } diff --git a/src/tools/bathelpers/README.md b/src/test/java/tools/bathelpers/README.md similarity index 100% rename from src/tools/bathelpers/README.md rename to src/test/java/tools/bathelpers/README.md diff --git a/src/tools/bathelpers/analyze_project.bat b/src/test/java/tools/bathelpers/analyze_project.bat similarity index 100% rename from src/tools/bathelpers/analyze_project.bat rename to src/test/java/tools/bathelpers/analyze_project.bat diff --git a/src/tools/bathelpers/build_project.bat b/src/test/java/tools/bathelpers/build_project.bat similarity index 100% rename from src/tools/bathelpers/build_project.bat rename to src/test/java/tools/bathelpers/build_project.bat diff --git a/src/tools/bathelpers/list_files.bat b/src/test/java/tools/bathelpers/list_files.bat similarity index 100% rename from src/tools/bathelpers/list_files.bat rename to src/test/java/tools/bathelpers/list_files.bat diff --git a/src/tools/bathelpers/move_plugin.bat b/src/test/java/tools/bathelpers/move_plugin.bat similarity index 100% rename from src/tools/bathelpers/move_plugin.bat rename to src/test/java/tools/bathelpers/move_plugin.bat diff --git a/src/tools/bathelpers/quick_build.bat b/src/test/java/tools/bathelpers/quick_build.bat similarity index 100% rename from src/tools/bathelpers/quick_build.bat rename to src/test/java/tools/bathelpers/quick_build.bat diff --git a/src/tools/bathelpers/run_server.bat b/src/test/java/tools/bathelpers/run_server.bat similarity index 100% rename from src/tools/bathelpers/run_server.bat rename to src/test/java/tools/bathelpers/run_server.bat diff --git a/src/tools/bathelpers/setvars.bat b/src/test/java/tools/bathelpers/setvars.bat similarity index 100% rename from src/tools/bathelpers/setvars.bat rename to src/test/java/tools/bathelpers/setvars.bat diff --git a/src/test/java/tools/checktestmocks/CheckTestMocks.java b/src/test/java/tools/checktestmocks/CheckTestMocks.java new file mode 100644 index 00000000..2508cdd6 --- /dev/null +++ b/src/test/java/tools/checktestmocks/CheckTestMocks.java @@ -0,0 +1,171 @@ +package tools.checktestmocks; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; +import fr.xephi.authme.initialization.ConstructorInjection; +import fr.xephi.authme.initialization.FieldInjection; +import fr.xephi.authme.initialization.Injection; +import fr.xephi.authme.util.StringUtils; +import org.mockito.Mock; +import tools.utils.AutoToolTask; +import tools.utils.ToolsConstants; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; + +/** + * Task checking if all tests' {@code @Mock} fields have a corresponding + * {@code @Inject} field in the class they are testing. + */ +public class CheckTestMocks implements AutoToolTask { + + private List errors = new ArrayList<>(); + + @Override + public String getTaskName() { + return "checkTestMocks"; + } + + @Override + public void execute(Scanner scanner) { + executeDefault(); + } + + @Override + public void executeDefault() { + readAndCheckFiles(new File(ToolsConstants.TEST_SOURCE_ROOT)); + System.out.println(StringUtils.join("\n", errors)); + } + + /** + * Recursively reads directories and checks the contained classes. + * + * @param dir the directory to read + */ + private void readAndCheckFiles(File dir) { + File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalStateException("Cannot read folder '" + dir + "'"); + } + for (File file : files) { + if (file.isDirectory()) { + readAndCheckFiles(file); + } else if (file.isFile()) { + Class clazz = loadTestClass(file); + if (clazz != null) { + checkClass(clazz); + } + // else System.out.format("No @Mock fields found in class of file '%s'%n", file.getName()) + } + } + } + + /** + * Checks the given test class' @Mock fields against the corresponding production class' @Inject fields. + * + * @param testClass the test class to verify + */ + private void checkClass(Class testClass) { + Class realClass = returnRealClass(testClass); + if (realClass != null) { + Set> mockFields = getMocks(testClass); + Set> injectFields = getRealClassDependencies(realClass); + if (!injectFields.containsAll(mockFields)) { + addErrorEntry(testClass, "Error - Found the following mocks absent as @Inject: " + + formatClassList(Sets.difference(mockFields, injectFields))); + } else if (!mockFields.containsAll(injectFields)) { + addErrorEntry(testClass, "Info - Found @Inject fields which are not present as @Mock: " + + formatClassList(Sets.difference(injectFields, mockFields))); + } + } + } + + private void addErrorEntry(Class clazz, String message) { + errors.add(clazz.getSimpleName() + ": " + message); + } + + private static Class loadTestClass(File file) { + String fileName = file.getPath(); + String className = fileName + // Strip source folders and .java ending + .substring("src/test/java/".length(), fileName.length() - 5) + .replace(File.separator, "."); + try { + Class clazz = CheckTestMocks.class.getClassLoader().loadClass(className); + return isTestClassWithMocks(clazz) ? clazz : null; + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException(e); + } + } + + private static Set> getMocks(Class clazz) { + Set> result = new HashSet<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(Mock.class)) { + result.add(field.getType()); + } + } + return result; + } + + /** + * Returns the production class ("real class") corresponding to the given test class. + * Returns null if the production class could not be mapped or loaded. + * + * @param testClass the test class to find the corresponding production class for + * @return production class, or null if not found + */ + private static Class returnRealClass(Class testClass) { + String testClassName = testClass.getName(); + String realClassName = testClassName.replaceAll("(Integration|Consistency)?Test$", ""); + if (realClassName.equals(testClassName)) { + System.out.format("%s doesn't match typical test class naming pattern.%n", testClassName); + return null; + } + try { + return CheckTestMocks.class.getClassLoader().loadClass(realClassName); + } catch (ClassNotFoundException e) { + System.out.format("Real class '%s' not found for test class '%s'%n", realClassName, testClassName); + return null; + } + } + + private static Set> getRealClassDependencies(Class realClass) { + Injection injection = ConstructorInjection.provide(realClass).get(); + if (injection != null) { + return Sets.newHashSet(injection.getDependencies()); + } + injection = FieldInjection.provide(realClass).get(); + return injection == null + ? Collections.>emptySet() + : Sets.newHashSet(injection.getDependencies()); + } + + private static boolean isTestClassWithMocks(Class clazz) { + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(Mock.class)) { + return true; + } + } + return false; + } + + private static String formatClassList(Collection> coll) { + Collection classNames = Collections2.transform(coll, new Function, String>() { + @Override + public String apply(Class input) { + return input.getSimpleName(); + } + }); + return StringUtils.join(", ", classNames); + } + +} diff --git a/src/tools/commands/CommandPageCreater.java b/src/test/java/tools/commands/CommandPageCreater.java similarity index 76% rename from src/tools/commands/CommandPageCreater.java rename to src/test/java/tools/commands/CommandPageCreater.java index 8d92dbfb..d5375a2f 100644 --- a/src/tools/commands/CommandPageCreater.java +++ b/src/test/java/tools/commands/CommandPageCreater.java @@ -1,22 +1,21 @@ -package commands; +package tools.commands; import fr.xephi.authme.command.CommandArgumentDescription; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandInitializer; -import fr.xephi.authme.command.CommandPermissions; import fr.xephi.authme.command.CommandUtils; import fr.xephi.authme.permission.PermissionNode; -import utils.FileUtils; -import utils.TagValue.NestedTagValue; -import utils.TagValueHolder; -import utils.ToolTask; -import utils.ToolsConstants; +import tools.utils.AutoToolTask; +import tools.utils.FileUtils; +import tools.utils.TagValue.NestedTagValue; +import tools.utils.TagValueHolder; +import tools.utils.ToolsConstants; import java.util.Collection; import java.util.Scanner; import java.util.Set; -public class CommandPageCreater implements ToolTask { +public class CommandPageCreater implements AutoToolTask { private static final String OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "commands.md"; @@ -27,7 +26,13 @@ public class CommandPageCreater implements ToolTask { @Override public void execute(Scanner scanner) { - final Set baseCommands = CommandInitializer.buildCommands(); + executeDefault(); + } + + @Override + public void executeDefault() { + CommandInitializer commandInitializer = new CommandInitializer(); + final Set baseCommands = commandInitializer.getCommands(); NestedTagValue commandTags = new NestedTagValue(); addCommandsInfo(commandTags, baseCommands); @@ -44,7 +49,7 @@ public class CommandPageCreater implements ToolTask { .put("command", CommandUtils.constructCommandPath(command)) .put("description", command.getDetailedDescription()) .put("arguments", formatArguments(command.getArguments())) - .put("permissions", formatPermissions(command.getCommandPermissions())); + .put("permissions", formatPermissions(command.getPermission())); commandTags.add(tags); if (!command.getChildren().isEmpty()) { @@ -53,15 +58,12 @@ public class CommandPageCreater implements ToolTask { } } - private static String formatPermissions(CommandPermissions permissions) { - if (permissions == null) { + private static String formatPermissions(PermissionNode permission) { + if (permission == null) { return ""; + } else { + return permission.getNode(); } - String result = ""; - for (PermissionNode node : permissions.getPermissionNodes()) { - result += node.getNode() + " "; - } - return result.trim(); } private static String formatArguments(Iterable arguments) { diff --git a/src/tools/commands/commands.tpl.md b/src/test/java/tools/commands/commands.tpl.md similarity index 100% rename from src/tools/commands/commands.tpl.md rename to src/test/java/tools/commands/commands.tpl.md diff --git a/src/test/java/tools/dependencygraph/DrawDependency.java b/src/test/java/tools/dependencygraph/DrawDependency.java new file mode 100644 index 00000000..4f9dd507 --- /dev/null +++ b/src/test/java/tools/dependencygraph/DrawDependency.java @@ -0,0 +1,199 @@ +package tools.dependencygraph; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.converter.Converter; +import fr.xephi.authme.initialization.ConstructorInjection; +import fr.xephi.authme.initialization.FieldInjection; +import fr.xephi.authme.initialization.Injection; +import fr.xephi.authme.process.AsynchronousProcess; +import fr.xephi.authme.process.SynchronousProcess; +import fr.xephi.authme.security.crypts.EncryptionMethod; +import org.bukkit.event.Listener; +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Scanner; + +/** + * Creates a DOT file of the dependencies in AuthMe classes. + */ +public class DrawDependency implements ToolTask { + + private static final String DOT_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "dependencygraph/graph.dot"; + // Package root + private static final String ROOT_PACKAGE = "fr.xephi.authme"; + + private static final List> SUPER_TYPES = ImmutableList.of(ExecutableCommand.class, + SynchronousProcess.class, AsynchronousProcess.class, EncryptionMethod.class, Converter.class, Listener.class); + + private boolean mapToSupertype; + // Map with the graph's nodes: value is one of the key's dependencies + private Multimap, String> foundDependencies = HashMultimap.create(); + + @Override + public String getTaskName() { + return "drawDependencyGraph"; + } + + @Override + public void execute(Scanner scanner) { + System.out.println("Summarize classes to their generic super type where applicable?"); + mapToSupertype = "y".equalsIgnoreCase(scanner.nextLine()); + + // Gather all connections + readAndProcessFiles(new File(ToolsConstants.MAIN_SOURCE_ROOT)); + + // Prompt user for simplification of graph + System.out.println("Do you want to remove classes that are not used as dependency elsewhere?"); + System.out.println("Specify the number of times to do this: [0=keep all]"); + int stripVerticesCount; + try { + stripVerticesCount = Integer.valueOf(scanner.nextLine()); + } catch (NumberFormatException e) { + stripVerticesCount = 0; + } + + // Perform simplification as per user's wish + for (int i = 0; i < stripVerticesCount; ++i) { + stripNodesWithNoOutgoingEdges(); + } + + // Create dot file content + final String pattern = "\t\"%s\" -> \"%s\";"; + String dotFile = ""; + for (Map.Entry, String> entry : foundDependencies.entries()) { + dotFile += "\n" + String.format(pattern, entry.getValue(), entry.getKey().getSimpleName()); + } + + // Write dot file + try { + Files.write(Paths.get(DOT_FILE), ("digraph G {\n" + dotFile + "\n}").getBytes()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + System.out.println("Graph file written"); + System.out.format("Run 'dot -Tpng %s -o graph.png' to generate image (requires GraphViz)%n", DOT_FILE); + } + + /** + * Recursively reads the given directory and processes the files. + * + * @param dir the directory to read + */ + private void readAndProcessFiles(File dir) { + File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalStateException("Cannot read folder '" + dir + "'"); + } + for (File file : files) { + if (file.isDirectory()) { + readAndProcessFiles(file); + } else if (file.isFile()) { + Class clazz = loadClass(file); + if (clazz != null) { + processClass(clazz); + } + } + } + } + + private void processClass(Class clazz) { + List dependencies = getDependencies(clazz); + if (dependencies != null) { + foundDependencies.putAll(mapToSuper(clazz), dependencies); + } + } + + private Class mapToSuper(Class clazz) { + if (!mapToSupertype || clazz == null) { + return clazz; + } + for (Class parent : SUPER_TYPES) { + if (parent.isAssignableFrom(clazz)) { + return parent; + } + } + return clazz; + } + + // Load Class object for the class in the given file + private static Class loadClass(File file) { + final String fileName = file.getPath().replace(File.separator, "."); + if (!fileName.endsWith(".java")) { + return null; + } + final String className = fileName + .substring(fileName.indexOf(ROOT_PACKAGE), fileName.length() - ".java".length()); + try { + return DrawDependency.class.getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + + private List getDependencies(Class clazz) { + Injection injection = ConstructorInjection.provide(clazz).get(); + if (injection != null) { + return formatInjectionDependencies(injection); + } + injection = FieldInjection.provide(clazz).get(); + return injection == null ? null : formatInjectionDependencies(injection); + } + + /** + * Formats the dependencies returned by the given injection appropriately: + * if the dependency has an annotation, the annotation will be returned; + * otherwise the class name. + * + * @param injection the injection whose dependencies should be formatted + * @return list of dependencies in a friendly format + */ + private List formatInjectionDependencies(Injection injection) { + Class[] dependencies = injection.getDependencies(); + Class[] annotations = injection.getDependencyAnnotations(); + + List result = new ArrayList<>(dependencies.length); + for (int i = 0; i < dependencies.length; ++i) { + if (annotations[i] != null) { + result.add("@" + annotations[i].getSimpleName()); + } else { + result.add(mapToSuper(dependencies[i]).getSimpleName()); + } + } + return result; + } + + /** + * Removes all vertices in the graph that have no outgoing edges, i.e. all classes + * in the graph that only receive dependencies but are not used as a dependency anywhere. + * This process can be repeated multiple times. + */ + private void stripNodesWithNoOutgoingEdges() { + Map dependencies = new HashMap<>(); + for (Map.Entry, String> entry : foundDependencies.entries()) { + final String className = entry.getKey().getSimpleName(); + dependencies.put(className, Boolean.FALSE); + dependencies.put(entry.getValue(), Boolean.TRUE); + } + + Iterator> it = foundDependencies.keys().iterator(); + while (it.hasNext()) { + Class clazz = it.next(); + if (Boolean.FALSE.equals(dependencies.get(clazz.getSimpleName()))) { + it.remove(); + } + } + } +} diff --git a/src/test/java/tools/dependencygraph/graph_stripped1time_20160612.dot b/src/test/java/tools/dependencygraph/graph_stripped1time_20160612.dot new file mode 100644 index 00000000..afaaf9f2 --- /dev/null +++ b/src/test/java/tools/dependencygraph/graph_stripped1time_20160612.dot @@ -0,0 +1,60 @@ +digraph G { + + "PermissionsManager" -> "ValidationService"; + "NewSetting" -> "ValidationService"; + "DataSource" -> "ValidationService"; + "BukkitService" -> "AntiBot"; + "PermissionsManager" -> "AntiBot"; + "NewSetting" -> "AntiBot"; + "Messages" -> "AntiBot"; + "BukkitService" -> "TeleportationService"; + "PlayerCache" -> "TeleportationService"; + "NewSetting" -> "TeleportationService"; + "Messages" -> "TeleportationService"; + "SpawnLoader" -> "TeleportationService"; + "BukkitService" -> "SynchronousProcess"; + "PluginManager" -> "SynchronousProcess"; + "AuthMe" -> "SynchronousProcess"; + "TeleportationService" -> "SynchronousProcess"; + "LimboPlayerTaskManager" -> "SynchronousProcess"; + "ProcessService" -> "SynchronousProcess"; + "LimboCache" -> "SynchronousProcess"; + "DataSource" -> "SynchronousProcess"; + "BukkitService" -> "TempbanManager"; + "Messages" -> "TempbanManager"; + "NewSetting" -> "TempbanManager"; + "BukkitService" -> "LimboPlayerTaskManager"; + "PlayerCache" -> "LimboPlayerTaskManager"; + "Messages" -> "LimboPlayerTaskManager"; + "NewSetting" -> "LimboPlayerTaskManager"; + "LimboCache" -> "LimboPlayerTaskManager"; + "PluginManager" -> "PasswordSecurity"; + "AuthMeServiceInitializer" -> "PasswordSecurity"; + "NewSetting" -> "PasswordSecurity"; + "DataSource" -> "PasswordSecurity"; + "PluginManager" -> "PluginHooks"; + "Server" -> "PermissionsManager"; + "PluginManager" -> "PermissionsManager"; + "PermissionsManager" -> "LimboCache"; + "SpawnLoader" -> "LimboCache"; + "@DataFolder" -> "SpawnLoader"; + "NewSetting" -> "SpawnLoader"; + "PluginHooks" -> "SpawnLoader"; + "DataSource" -> "SpawnLoader"; + "BukkitService" -> "AsynchronousProcess"; + "CaptchaManager" -> "AsynchronousProcess"; + "SyncProcessManager" -> "AsynchronousProcess"; + "TempbanManager" -> "AsynchronousProcess"; + "PlayerCache" -> "AsynchronousProcess"; + "PasswordSecurity" -> "AsynchronousProcess"; + "LimboCache" -> "AsynchronousProcess"; + "DataSource" -> "AsynchronousProcess"; + "AuthMe" -> "AsynchronousProcess"; + "TeleportationService" -> "AsynchronousProcess"; + "LimboPlayerTaskManager" -> "AsynchronousProcess"; + "PermissionsManager" -> "AsynchronousProcess"; + "ValidationService" -> "AsynchronousProcess"; + "ProcessService" -> "AsynchronousProcess"; + "PluginHooks" -> "AsynchronousProcess"; + "AuthMe" -> "BukkitService"; +} \ No newline at end of file diff --git a/src/test/java/tools/dependencygraph/graph_stripped1time_20160612.png b/src/test/java/tools/dependencygraph/graph_stripped1time_20160612.png new file mode 100644 index 00000000..79d3b504 Binary files /dev/null and b/src/test/java/tools/dependencygraph/graph_stripped1time_20160612.png differ diff --git a/src/tools/docs/UpdateDocsTask.java b/src/test/java/tools/docs/UpdateDocsTask.java similarity index 51% rename from src/tools/docs/UpdateDocsTask.java rename to src/test/java/tools/docs/UpdateDocsTask.java index ccd0ed1b..48624afe 100644 --- a/src/tools/docs/UpdateDocsTask.java +++ b/src/test/java/tools/docs/UpdateDocsTask.java @@ -1,10 +1,11 @@ -package docs; +package tools.docs; import com.google.common.collect.ImmutableSet; -import commands.CommandPageCreater; -import hashmethods.HashAlgorithmsDescriptionTask; -import permissions.PermissionsListWriter; -import utils.ToolTask; +import tools.commands.CommandPageCreater; +import tools.hashmethods.HashAlgorithmsDescriptionTask; +import tools.permissions.PermissionsListWriter; +import tools.utils.AutoToolTask; +import tools.utils.ToolTask; import java.util.Scanner; import java.util.Set; @@ -12,9 +13,9 @@ import java.util.Set; /** * Task that runs all tasks which update files in the docs folder. */ -public class UpdateDocsTask implements ToolTask { +public class UpdateDocsTask implements AutoToolTask { - private final Set> TASKS = ImmutableSet.of( + private static final Set> TASKS = ImmutableSet.>of( CommandPageCreater.class, HashAlgorithmsDescriptionTask.class, PermissionsListWriter.class); @Override @@ -23,17 +24,25 @@ public class UpdateDocsTask implements ToolTask { } @Override - public void execute(Scanner scanner) { - for (Class taskClass : TASKS) { - try { - ToolTask task = instantiateTask(taskClass); - System.out.println("\nRunning " + task.getTaskName() + "\n-------------------"); + public void execute(final Scanner scanner) { + executeTasks(new TaskRunner() { + @Override + public void execute(ToolTask task) { task.execute(scanner); - } catch (UnsupportedOperationException e) { - System.err.println("Error running task of class '" + taskClass + "'"); - e.printStackTrace(); } - } + }); + } + + @Override + public void executeDefault() { + executeTasks(new TaskRunner() { + @Override + public void execute(ToolTask task) { + if (task instanceof AutoToolTask) { + ((AutoToolTask) task).executeDefault(); + } + } + }); } private static ToolTask instantiateTask(Class clazz) { @@ -43,4 +52,21 @@ public class UpdateDocsTask implements ToolTask { throw new UnsupportedOperationException(e); } } + + private static void executeTasks(TaskRunner runner) { + for (Class taskClass : TASKS) { + try { + ToolTask task = instantiateTask(taskClass); + System.out.println("\nRunning " + task.getTaskName() + "\n-------------------"); + runner.execute(task); + } catch (UnsupportedOperationException e) { + System.err.println("Error running task of class '" + taskClass + "'"); + e.printStackTrace(); + } + } + } + + private interface TaskRunner { + void execute(ToolTask task); + } } diff --git a/src/tools/hashmethods/EncryptionMethodInfoGatherer.java b/src/test/java/tools/hashmethods/EncryptionMethodInfoGatherer.java similarity index 81% rename from src/tools/hashmethods/EncryptionMethodInfoGatherer.java rename to src/test/java/tools/hashmethods/EncryptionMethodInfoGatherer.java index 4ade88ce..26af6f82 100644 --- a/src/tools/hashmethods/EncryptionMethodInfoGatherer.java +++ b/src/test/java/tools/hashmethods/EncryptionMethodInfoGatherer.java @@ -1,16 +1,16 @@ -package hashmethods; +package tools.hashmethods; +import fr.xephi.authme.initialization.AuthMeServiceInitializer; import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HexSaltedMethod; import fr.xephi.authme.security.crypts.description.AsciiRestricted; import fr.xephi.authme.security.crypts.description.HasSalt; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; -import org.mockito.BDDMockito; +import fr.xephi.authme.settings.domain.Property; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.lang.annotation.Annotation; import java.util.HashMap; @@ -19,7 +19,9 @@ import java.util.Map; import java.util.Set; import static com.google.common.collect.Sets.newHashSet; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * Gathers information on {@link EncryptionMethod} implementations based on @@ -31,7 +33,7 @@ public class EncryptionMethodInfoGatherer { private final static Set> RELEVANT_ANNOTATIONS = newHashSet(HasSalt.class, Recommendation.class, AsciiRestricted.class); - private static NewSetting settings = createSettings(); + private static AuthMeServiceInitializer initializer = createInitializer(); private Map descriptions; @@ -55,7 +57,7 @@ public class EncryptionMethodInfoGatherer { private static MethodDescription createDescription(HashAlgorithm algorithm) { Class clazz = algorithm.getClazz(); - EncryptionMethod method = PasswordSecurity.initializeEncryptionMethod(algorithm, settings); + EncryptionMethod method = initializer.newInstance(clazz); if (method == null) { throw new NullPointerException("Method for '" + algorithm + "' is null"); } @@ -135,19 +137,23 @@ public class EncryptionMethodInfoGatherer { return key.cast(map.get(key)); } - private static NewSetting createSettings() { - // TODO #672 Don't mock settings but instantiate a NewSetting object without any validation / migration + @SuppressWarnings("unchecked") + private static AuthMeServiceInitializer createInitializer() { NewSetting settings = mock(NewSetting.class); - BDDMockito.given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); - BDDMockito.given(settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH)).willReturn(8); - return settings; + // Return the default value for any property + when(settings.getProperty(any(Property.class))).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Property property = (Property) invocation.getArguments()[0]; + return property.getDefaultValue(); + } + }); - /*try (InputStreamReader isr = new InputStreamReader(getClass().getResourceAsStream("config.yml"))) { - FileConfiguration configuration = YamlConfiguration.loadConfiguration(isr); - return new NewSetting(configuration, null, null, null); - } catch (IOException e) { - throw new UnsupportedOperationException(e); - }*/ + // By not passing any "allowed package" to the constructor, the initializer will throw if it needs to + // instantiate any dependency other than what we provide. + AuthMeServiceInitializer initializer = new AuthMeServiceInitializer(); + initializer.register(NewSetting.class, settings); + return initializer; } } diff --git a/src/tools/hashmethods/HashAlgorithmsDescriptionTask.java b/src/test/java/tools/hashmethods/HashAlgorithmsDescriptionTask.java similarity index 89% rename from src/tools/hashmethods/HashAlgorithmsDescriptionTask.java rename to src/test/java/tools/hashmethods/HashAlgorithmsDescriptionTask.java index 6d1a57ed..edbf5430 100644 --- a/src/tools/hashmethods/HashAlgorithmsDescriptionTask.java +++ b/src/test/java/tools/hashmethods/HashAlgorithmsDescriptionTask.java @@ -1,11 +1,11 @@ -package hashmethods; +package tools.hashmethods; import fr.xephi.authme.security.HashAlgorithm; -import utils.FileUtils; -import utils.TagValue.NestedTagValue; -import utils.TagValueHolder; -import utils.ToolTask; -import utils.ToolsConstants; +import tools.utils.AutoToolTask; +import tools.utils.FileUtils; +import tools.utils.TagValue.NestedTagValue; +import tools.utils.TagValueHolder; +import tools.utils.ToolsConstants; import java.util.Map; import java.util.Scanner; @@ -15,13 +15,18 @@ import java.util.Scanner; * * @see {@link fr.xephi.authme.security.HashAlgorithm} */ -public class HashAlgorithmsDescriptionTask implements ToolTask { +public class HashAlgorithmsDescriptionTask implements AutoToolTask { private static final String CUR_FOLDER = ToolsConstants.TOOLS_SOURCE_ROOT + "hashmethods/"; private static final String OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "hash_algorithms.md"; @Override public void execute(Scanner scanner) { + executeDefault(); + } + + @Override + public void executeDefault() { // Gather info and construct a row for each method EncryptionMethodInfoGatherer infoGatherer = new EncryptionMethodInfoGatherer(); Map descriptions = infoGatherer.getDescriptions(); diff --git a/src/tools/hashmethods/MethodDescription.java b/src/test/java/tools/hashmethods/MethodDescription.java similarity index 98% rename from src/tools/hashmethods/MethodDescription.java rename to src/test/java/tools/hashmethods/MethodDescription.java index 45aaf448..58765467 100644 --- a/src/tools/hashmethods/MethodDescription.java +++ b/src/test/java/tools/hashmethods/MethodDescription.java @@ -1,4 +1,4 @@ -package hashmethods; +package tools.hashmethods; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.description.SaltType; diff --git a/src/tools/hashmethods/hash_algorithms.tpl.md b/src/test/java/tools/hashmethods/hash_algorithms.tpl.md similarity index 100% rename from src/tools/hashmethods/hash_algorithms.tpl.md rename to src/test/java/tools/hashmethods/hash_algorithms.tpl.md diff --git a/src/tools/messages/MessageFileVerifier.java b/src/test/java/tools/messages/MessageFileVerifier.java similarity index 99% rename from src/tools/messages/MessageFileVerifier.java rename to src/test/java/tools/messages/MessageFileVerifier.java index 68e07ea4..1157284f 100644 --- a/src/tools/messages/MessageFileVerifier.java +++ b/src/test/java/tools/messages/MessageFileVerifier.java @@ -1,4 +1,4 @@ -package messages; +package tools.messages; import com.google.common.base.Predicate; import com.google.common.collect.HashMultimap; @@ -8,7 +8,7 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.util.StringUtils; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import utils.FileUtils; +import tools.utils.FileUtils; import java.io.File; import java.util.ArrayList; diff --git a/src/tools/messages/README.md b/src/test/java/tools/messages/README.md similarity index 100% rename from src/tools/messages/README.md rename to src/test/java/tools/messages/README.md diff --git a/src/tools/messages/VerifyMessagesTask.java b/src/test/java/tools/messages/VerifyMessagesTask.java similarity index 95% rename from src/tools/messages/VerifyMessagesTask.java rename to src/test/java/tools/messages/VerifyMessagesTask.java index 28b42b71..de888be7 100644 --- a/src/tools/messages/VerifyMessagesTask.java +++ b/src/test/java/tools/messages/VerifyMessagesTask.java @@ -1,11 +1,11 @@ -package messages; +package tools.messages; import com.google.common.collect.Multimap; import fr.xephi.authme.util.StringUtils; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import utils.ToolTask; -import utils.ToolsConstants; +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; import java.io.File; import java.util.ArrayList; @@ -133,7 +133,7 @@ public final class VerifyMessagesTask implements ToolTask { File folder = new File(MESSAGES_FOLDER); File[] files = folder.listFiles(); if (files == null) { - throw new RuntimeException("Could not read files from folder '" + folder.getName() + "'"); + throw new IllegalStateException("Could not read files from folder '" + folder.getName() + "'"); } List messageFiles = new ArrayList<>(); @@ -143,7 +143,7 @@ public final class VerifyMessagesTask implements ToolTask { } } if (messageFiles.isEmpty()) { - throw new RuntimeException("Error getting message files: list of files is empty"); + throw new IllegalStateException("Error getting message files: list of files is empty"); } return messageFiles; } diff --git a/src/tools/messages/translation/AuthMeYamlConfiguration.java b/src/test/java/tools/messages/translation/AuthMeYamlConfiguration.java similarity index 98% rename from src/tools/messages/translation/AuthMeYamlConfiguration.java rename to src/test/java/tools/messages/translation/AuthMeYamlConfiguration.java index f8724dff..21564b57 100644 --- a/src/tools/messages/translation/AuthMeYamlConfiguration.java +++ b/src/test/java/tools/messages/translation/AuthMeYamlConfiguration.java @@ -1,4 +1,4 @@ -package messages.translation; +package tools.messages.translation; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; diff --git a/src/tools/messages/translation/ExportMessagesTask.java b/src/test/java/tools/messages/translation/ExportMessagesTask.java similarity index 97% rename from src/tools/messages/translation/ExportMessagesTask.java rename to src/test/java/tools/messages/translation/ExportMessagesTask.java index c281696b..672dac39 100644 --- a/src/tools/messages/translation/ExportMessagesTask.java +++ b/src/test/java/tools/messages/translation/ExportMessagesTask.java @@ -1,4 +1,4 @@ -package messages.translation; +package tools.messages.translation; import com.google.common.io.CharStreams; import com.google.gson.Gson; @@ -6,8 +6,8 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.util.StringUtils; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import utils.ToolTask; -import utils.ToolsConstants; +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; import java.io.File; import java.io.IOException; diff --git a/src/tools/messages/translation/ImportMessagesTask.java b/src/test/java/tools/messages/translation/ImportMessagesTask.java similarity index 95% rename from src/tools/messages/translation/ImportMessagesTask.java rename to src/test/java/tools/messages/translation/ImportMessagesTask.java index 1edcdf04..320edb01 100644 --- a/src/tools/messages/translation/ImportMessagesTask.java +++ b/src/test/java/tools/messages/translation/ImportMessagesTask.java @@ -1,15 +1,15 @@ -package messages.translation; +package tools.messages.translation; import com.google.common.io.Resources; import com.google.gson.Gson; import fr.xephi.authme.output.MessageKey; -import messages.MessageFileVerifier; -import messages.VerifyMessagesTask; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import utils.FileUtils; -import utils.ToolTask; -import utils.ToolsConstants; +import tools.messages.MessageFileVerifier; +import tools.messages.VerifyMessagesTask; +import tools.utils.FileUtils; +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; import java.io.File; import java.io.IOException; diff --git a/src/tools/messages/translation/LanguageExport.java b/src/test/java/tools/messages/translation/LanguageExport.java similarity index 91% rename from src/tools/messages/translation/LanguageExport.java rename to src/test/java/tools/messages/translation/LanguageExport.java index 3be0fa44..c15a54bd 100644 --- a/src/tools/messages/translation/LanguageExport.java +++ b/src/test/java/tools/messages/translation/LanguageExport.java @@ -1,4 +1,4 @@ -package messages.translation; +package tools.messages.translation; import java.util.Collections; import java.util.List; diff --git a/src/tools/messages/translation/MessageExport.java b/src/test/java/tools/messages/translation/MessageExport.java similarity index 93% rename from src/tools/messages/translation/MessageExport.java rename to src/test/java/tools/messages/translation/MessageExport.java index da2c0874..270c460d 100644 --- a/src/tools/messages/translation/MessageExport.java +++ b/src/test/java/tools/messages/translation/MessageExport.java @@ -1,4 +1,4 @@ -package messages.translation; +package tools.messages.translation; import fr.xephi.authme.util.StringUtils; diff --git a/src/tools/messages/translation/WriteAllExportsTask.java b/src/test/java/tools/messages/translation/WriteAllExportsTask.java similarity index 92% rename from src/tools/messages/translation/WriteAllExportsTask.java rename to src/test/java/tools/messages/translation/WriteAllExportsTask.java index 413943d4..f5f3825b 100644 --- a/src/tools/messages/translation/WriteAllExportsTask.java +++ b/src/test/java/tools/messages/translation/WriteAllExportsTask.java @@ -1,9 +1,9 @@ -package messages.translation; +package tools.messages.translation; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import utils.FileUtils; -import utils.ToolsConstants; +import tools.utils.FileUtils; +import tools.utils.ToolsConstants; import java.io.File; import java.util.Scanner; diff --git a/src/tools/messages/translation/export/export-is-written-here b/src/test/java/tools/messages/translation/export/export-is-written-here similarity index 100% rename from src/tools/messages/translation/export/export-is-written-here rename to src/test/java/tools/messages/translation/export/export-is-written-here diff --git a/src/tools/permissions/PermissionNodesGatherer.java b/src/test/java/tools/permissions/PermissionNodesGatherer.java similarity index 97% rename from src/tools/permissions/PermissionNodesGatherer.java rename to src/test/java/tools/permissions/PermissionNodesGatherer.java index bfde6c87..58b5df4e 100644 --- a/src/tools/permissions/PermissionNodesGatherer.java +++ b/src/test/java/tools/permissions/PermissionNodesGatherer.java @@ -1,11 +1,11 @@ -package permissions; +package tools.permissions; import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerStatePermission; -import utils.FileUtils; -import utils.ToolsConstants; +import tools.utils.FileUtils; +import tools.utils.ToolsConstants; import java.util.EnumSet; import java.util.HashMap; diff --git a/src/tools/permissions/PermissionsListWriter.java b/src/test/java/tools/permissions/PermissionsListWriter.java similarity index 88% rename from src/tools/permissions/PermissionsListWriter.java rename to src/test/java/tools/permissions/PermissionsListWriter.java index dc16cea2..e0c43b22 100644 --- a/src/tools/permissions/PermissionsListWriter.java +++ b/src/test/java/tools/permissions/PermissionsListWriter.java @@ -1,10 +1,10 @@ -package permissions; +package tools.permissions; -import utils.FileUtils; -import utils.TagValue.NestedTagValue; -import utils.TagValueHolder; -import utils.ToolTask; -import utils.ToolsConstants; +import tools.utils.AutoToolTask; +import tools.utils.FileUtils; +import tools.utils.TagValue.NestedTagValue; +import tools.utils.TagValueHolder; +import tools.utils.ToolsConstants; import java.util.Map; import java.util.Scanner; @@ -14,7 +14,7 @@ import java.util.Set; * Task responsible for formatting a permissions node list and * for writing it to a file if desired. */ -public class PermissionsListWriter implements ToolTask { +public class PermissionsListWriter implements AutoToolTask { private static final String PERMISSIONS_OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "permission_nodes.md"; @@ -44,6 +44,11 @@ public class PermissionsListWriter implements ToolTask { } } + @Override + public void executeDefault() { + generateAndWriteFile(); + } + private static void generateAndWriteFile() { final NestedTagValue permissionsTagValue = generatePermissionsList(); diff --git a/src/tools/permissions/README.md b/src/test/java/tools/permissions/README.md similarity index 100% rename from src/tools/permissions/README.md rename to src/test/java/tools/permissions/README.md diff --git a/src/tools/permissions/permission_nodes.tpl.md b/src/test/java/tools/permissions/permission_nodes.tpl.md similarity index 100% rename from src/tools/permissions/permission_nodes.tpl.md rename to src/test/java/tools/permissions/permission_nodes.tpl.md diff --git a/src/tools/shhelpers/analyze_project.sh b/src/test/java/tools/shhelpers/analyze_project.sh similarity index 100% rename from src/tools/shhelpers/analyze_project.sh rename to src/test/java/tools/shhelpers/analyze_project.sh diff --git a/src/tools/shhelpers/build_project.sh b/src/test/java/tools/shhelpers/build_project.sh similarity index 100% rename from src/tools/shhelpers/build_project.sh rename to src/test/java/tools/shhelpers/build_project.sh diff --git a/src/tools/shhelpers/list_files.sh b/src/test/java/tools/shhelpers/list_files.sh similarity index 100% rename from src/tools/shhelpers/list_files.sh rename to src/test/java/tools/shhelpers/list_files.sh diff --git a/src/tools/shhelpers/move_plugin.sh b/src/test/java/tools/shhelpers/move_plugin.sh similarity index 100% rename from src/tools/shhelpers/move_plugin.sh rename to src/test/java/tools/shhelpers/move_plugin.sh diff --git a/src/tools/shhelpers/quick_build.sh b/src/test/java/tools/shhelpers/quick_build.sh similarity index 100% rename from src/tools/shhelpers/quick_build.sh rename to src/test/java/tools/shhelpers/quick_build.sh diff --git a/src/tools/shhelpers/run_server.sh b/src/test/java/tools/shhelpers/run_server.sh similarity index 100% rename from src/tools/shhelpers/run_server.sh rename to src/test/java/tools/shhelpers/run_server.sh diff --git a/src/tools/shhelpers/setvars.sh b/src/test/java/tools/shhelpers/setvars.sh similarity index 100% rename from src/tools/shhelpers/setvars.sh rename to src/test/java/tools/shhelpers/setvars.sh diff --git a/src/tools/shhelpers/sort_file_content.sh b/src/test/java/tools/shhelpers/sort_file_content.sh similarity index 100% rename from src/tools/shhelpers/sort_file_content.sh rename to src/test/java/tools/shhelpers/sort_file_content.sh diff --git a/src/test/java/tools/utils/AutoToolTask.java b/src/test/java/tools/utils/AutoToolTask.java new file mode 100644 index 00000000..b635fe5f --- /dev/null +++ b/src/test/java/tools/utils/AutoToolTask.java @@ -0,0 +1,13 @@ +package tools.utils; + +/** + * Interface for tasks that can be run automatically, i.e. without any user input. + */ +public interface AutoToolTask extends ToolTask { + + /** + * Execute the task with default settings. + */ + void executeDefault(); + +} diff --git a/src/tools/utils/FileUtils.java b/src/test/java/tools/utils/FileUtils.java similarity index 77% rename from src/tools/utils/FileUtils.java rename to src/test/java/tools/utils/FileUtils.java index 853965e9..acc260de 100644 --- a/src/tools/utils/FileUtils.java +++ b/src/test/java/tools/utils/FileUtils.java @@ -1,4 +1,4 @@ -package utils; +package tools.utils; import java.io.IOException; import java.nio.charset.Charset; @@ -27,7 +27,7 @@ public final class FileUtils { try { Files.write(Paths.get(outputFile), contents.getBytes()); } catch (IOException e) { - throw new RuntimeException("Failed to write to file '" + outputFile + "'", e); + throw new UnsupportedOperationException("Failed to write to file '" + outputFile + "'", e); } } @@ -35,7 +35,7 @@ public final class FileUtils { try { Files.write(Paths.get(outputFile), contents.getBytes(), StandardOpenOption.APPEND); } catch (IOException e) { - throw new RuntimeException("Failed to append to file '" + outputFile + "'", e); + throw new UnsupportedOperationException("Failed to append to file '" + outputFile + "'", e); } } @@ -43,7 +43,7 @@ public final class FileUtils { try { return new String(Files.readAllBytes(Paths.get(file)), CHARSET); } catch (IOException e) { - throw new RuntimeException("Could not read from file '" + file + "'", e); + throw new UnsupportedOperationException("Could not read from file '" + file + "'", e); } } @@ -51,7 +51,7 @@ public final class FileUtils { try { return Files.readAllLines(Paths.get(file), CHARSET); } catch (IOException e) { - throw new RuntimeException("Could not read from file '" + file + "'", e); + throw new UnsupportedOperationException("Could not read from file '" + file + "'", e); } } diff --git a/src/tools/utils/TagReplacer.java b/src/test/java/tools/utils/TagReplacer.java similarity index 97% rename from src/tools/utils/TagReplacer.java rename to src/test/java/tools/utils/TagReplacer.java index 5efe4c8b..9b5a74bb 100644 --- a/src/tools/utils/TagReplacer.java +++ b/src/test/java/tools/utils/TagReplacer.java @@ -1,7 +1,7 @@ -package utils; +package tools.utils; -import utils.TagValue.NestedTagValue; -import utils.TagValue.TextTagValue; +import tools.utils.TagValue.NestedTagValue; +import tools.utils.TagValue.TextTagValue; import java.util.Date; import java.util.Map; diff --git a/src/tools/utils/TagValue.java b/src/test/java/tools/utils/TagValue.java similarity index 97% rename from src/tools/utils/TagValue.java rename to src/test/java/tools/utils/TagValue.java index 7ca66b3d..ad73a816 100644 --- a/src/tools/utils/TagValue.java +++ b/src/test/java/tools/utils/TagValue.java @@ -1,4 +1,4 @@ -package utils; +package tools.utils; import java.util.ArrayList; import java.util.List; diff --git a/src/tools/utils/TagValueHolder.java b/src/test/java/tools/utils/TagValueHolder.java similarity index 90% rename from src/tools/utils/TagValueHolder.java rename to src/test/java/tools/utils/TagValueHolder.java index e2bd09b0..83862830 100644 --- a/src/tools/utils/TagValueHolder.java +++ b/src/test/java/tools/utils/TagValueHolder.java @@ -1,6 +1,6 @@ -package utils; +package tools.utils; -import utils.TagValue.TextTagValue; +import tools.utils.TagValue.TextTagValue; import java.util.HashMap; import java.util.Map; diff --git a/src/tools/utils/ToolTask.java b/src/test/java/tools/utils/ToolTask.java similarity index 96% rename from src/tools/utils/ToolTask.java rename to src/test/java/tools/utils/ToolTask.java index 56a2a788..d5cec152 100644 --- a/src/tools/utils/ToolTask.java +++ b/src/test/java/tools/utils/ToolTask.java @@ -1,4 +1,4 @@ -package utils; +package tools.utils; import java.util.Scanner; diff --git a/src/tools/utils/ToolsConstants.java b/src/test/java/tools/utils/ToolsConstants.java similarity index 61% rename from src/tools/utils/ToolsConstants.java rename to src/test/java/tools/utils/ToolsConstants.java index 504a1be7..6708fd77 100644 --- a/src/tools/utils/ToolsConstants.java +++ b/src/test/java/tools/utils/ToolsConstants.java @@ -1,21 +1,24 @@ -package utils; +package tools.utils; /** * Constants for the src/tools folder. */ public final class ToolsConstants { - private ToolsConstants() { - } - public static final String MAIN_SOURCE_ROOT = "src/main/java/"; public static final String MAIN_RESOURCES_ROOT = "src/main/resources/"; - public static final String TOOLS_SOURCE_ROOT = "src/tools/"; + // Add specific `fr.xephi.authme` package as not to include the tool tasks in the `tools` package + public static final String TEST_SOURCE_ROOT = "src/test/java/fr/xephi/authme"; + + public static final String TOOLS_SOURCE_ROOT = "src/test/java/tools/"; public static final String DOCS_FOLDER = "docs/"; public static final String DOCS_FOLDER_URL = "https://github.com/AuthMe-Team/AuthMeReloaded/tree/master/docs/"; + private ToolsConstants() { + } + } diff --git a/src/test/resources/converter/crazylogin.db b/src/test/resources/converter/crazylogin.db new file mode 100644 index 00000000..5498db81 --- /dev/null +++ b/src/test/resources/converter/crazylogin.db @@ -0,0 +1,3 @@ +name|password|ips|lastAction|loginFails|passwordExpired +qotaTo|8267663ab198a96437b9f455429a2c1b6c943111613c217bf2703c14d08a309d34e510ddb5549507b1500759dbcf9d4a99bc765ff37b32bd31adbb1e92e74ac5|127.0.0.1|2016-05-27 21:45:57|0|false +Bobby|ad50dbc841e6321210530801f5219a5ffb4c7c41f11878d465374a4b8db2965c50f69b6098918a58e4adea312e3633c7724b15e24a217009e6fa2b3c299d55f2|127.0.0.1|2016-05-27 21:45:40|0|false diff --git a/src/tools/README.md b/src/tools/README.md deleted file mode 100644 index b61dabea..00000000 --- a/src/tools/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# About src/tools -This _tools_ folder provides helpers and extended tests useful during the development of AuthMe. -This folder is not included during the build of AuthMe.