diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ac2e8b47..3ba13e0c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1 @@ -blank_issues_enabled: false +blank_issues_enabled: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 51a5ca89..a5e98502 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,5 @@ updates: - dependency-name: com.zaxxer:HikariCP - dependency-name: "org.mockito:mockito-core" # Mockito 5 requires Java 11 versions: ">= 5" + - dependency-name: "org.slf4j:slf4j-simple" + versions: ">= 1.7.36" diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 46d58c20..4d35b47a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -7,17 +7,41 @@ on: - master jobs: - build_and_test: + Build: strategy: matrix: - jdkversion: [11] + jdkversion: [ 11 ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: ${{ matrix.jdkversion }} - cache: 'maven' - - name: Build with Maven - run: mvn -V -B clean package --file pom.xml + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: ${{ matrix.jdkversion }} + cache: 'maven' + - name: Build + run: mvn -V -B clean package --file pom.xml + - name: Upload Artifacts + uses: actions/upload-artifact@v3.1.2 + with: + name: Download + path: ./target/AuthMe-5.6.0-FORK-Spigot-Universal.jar + runtime-test: + name: Plugin Runtime Test + needs: [Build] + runs-on: ubuntu-latest + strategy: + matrix: + include: + - mcVersion: '1.8.8' + javaVersion: '8' + - mcVersion: '1.12.2' + javaVersion: '8' + - mcVersion: '1.20.2' + javaVersion: '20' + steps: + - uses: HaHaWTH/minecraft-plugin-runtime-test@paper + with: + server-version: ${{ matrix.mcVersion }} + java-version: ${{ matrix.javaVersion }} + artifact-name: Download diff --git a/.gitignore b/.gitignore index 9a8a9ebf..e6225997 100644 --- a/.gitignore +++ b/.gitignore @@ -21,9 +21,10 @@ hs_err_pid* # Ignore IDEA directory .idea/* +.idea/ + # Include the project's code style settings file -!.idea/codeStyleSettings.xml # File-based project format: *.ipr diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..359bb530 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/README.md b/README.md index 9f57cf40..f482dd19 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,49 @@ -# AuthMeReloaded -**"The best authentication plugin for the Bukkit modding API!"** +# AuthMeReReloaded +**"A fork of the best authentication plugin for the Bukkit modding API!⭐"** -AuthMeLogo +![Graph](https://bstats.org/signatures/bukkit/AuthMeReloaded-Fork.svg) +

+ Code size + GitHub repo size + CodeFactor +

-| Type | Badges | -|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **General:** | ![](https://tokei.rs/b1/github/AuthMe/AuthMeReloaded?category=code) ![](https://tokei.rs/b1/github/AuthMe/AuthMeReloaded?category=files) | -| **Code quality:** | [![Code Climate](https://codeclimate.com/github/AuthMe/AuthMeReloaded/badges/gpa.svg)](https://codeclimate.com/github/AuthMe/AuthMeReloaded) [![Coverage status](https://coveralls.io/repos/AuthMe-Team/AuthMeReloaded/badge.svg?branch=master&service=github)](https://coveralls.io/github/AuthMe-Team/AuthMeReloaded?branch=master) | -| **Jenkins CI:** | [![Jenkins Status](https://img.shields.io/website-up-down-green-red/http/shields.io.svg?label=ci.codemc.org)](https://ci.codemc.org/) [![Build Status](https://ci.codemc.org/buildStatus/icon?job=AuthMe/AuthMeReloaded)](https://ci.codemc.org/job/AuthMe/job/AuthMeReloaded) ![Build Tests](https://img.shields.io/jenkins/t/https/ci.codemc.org/job/AuthMe/job/AuthMeReloaded.svg) | -| **Other CIs:** | [![Build Status](https://www.travis-ci.com/AuthMe/AuthMeReloaded.svg?branch=master)](https://www.travis-ci.com/AuthMe/AuthMeReloaded) | +**Detailed Changes:** + 1. Improved mail sending logic & support more emails + 2. Shutdown mail sending(When server is closed, email you) + 3. Legacy bug fixes + 4. Anti Ghost Player(Doubled login bug) + 5. Use the best performance method by server brand + 6. Bedrock Compatibility(Floodgate needed)(based on UUID) + 7. Update checker + 8. Integrated GUI Captcha feature(Bedrock compatibility & ProtocolLib needed)(70% Asynchronous) + 9. Improved listeners + 10. Player login logic improvement to reduce lag + 11. Automatically purge bot data + 12. Folia + compatibility [Here](https://github.com/HaHaWTH/AuthMeReReloaded/releases/download/b20/AuthMe-5.6.0-FORK-Folia.jar) + 13. Offhand Menu compatibility(Thats amazing) + 14. Automatically fix portal stuck issue + 15. Automatically login for Bedrock players(configurable) + 16. Fix shulker box crash bug on legacy versions(MC 1.13-) + 17. **H2 database support** + 18. **100% compatibility with original authme and extensions** + 19. More...... -## Description +**Download links:** +[Releases](https://github.com/HaHaWTH/AuthMeReReloaded/releases/latest) +[Actions(Dev builds, use at your own risk!)](https://github.com/HaHaWTH/AuthMeReReloaded/actions/workflows/maven.yml) -Prevent username stealing on your server!
-Use it to secure your Offline mode server or to increase your Online mode server's protection! +If you are using FRP(内网穿透) for your server, this plugin may help [HAProxy-Detector](https://github.com/HaHaWTH/HAProxy-Detector) -AuthMeReloaded disallows players who aren't authenticated to do actions like placing blocks, moving,
-typing commands or using the inventory. It can also kick players with uncommonly long or short player names or kick players from banned countries. +**Pull Requests and suggestions are welcome!** -With the Session Login feature you don't have to execute the authentication command every time you connect to the server! -Each command and every feature can be enabled or disabled from our well structured configuration file. - -You can also create your own translation file and, if you want, you can share it with us! :) - -#### Features: - - -#### Configuration -[How to configure AuthMe](https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/config.md) -#### Commands -[Command list and usage](https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/commands.md) -#### Permissions -- authme.player.* - for all user commands -- authme.admin.* - for all admin commands -- [List of all permission nodes](http://github.com/AuthMe/AuthMeReloaded/blob/master/docs/permission_nodes.md) - -#### How To -- [How to use the converter](https://github.com/AuthMe/AuthMeReloaded/wiki/Converters) -- [How to import database from xAuth](https://dev.bukkit.org/projects/authme-reloaded/pages/how-to-import-database-from-xauth) -- [Website integration](https://github.com/AuthMe/AuthMeReloaded/tree/master/samples/website_integration) -- [How to convert from Rakamak](https://dev.bukkit.org/projects/authme-reloaded/pages/how-to-import-database-from-rakamak) -- Convert between database types (e.g. SQLite to MySQL): /authme converter - - -## Links and Contacts - - - **Support:** - - [GitHub issue tracker](https://github.com/AuthMe/AuthMeReloaded/issues) - - [Discord](https://discord.gg/Vn9eCyE) - - [BukkitDev page](https://dev.bukkit.org/projects/authme-reloaded) - - [Spigot page](https://www.spigotmc.org/resources/authmereloaded.6269/) - -- **Dev resources:** - - JavaDocs - - Maven Repository - ```xml - - - codemc-repo - https://repo.codemc.org/repository/maven-public/ - - - - - - fr.xephi - authme - 5.6.0-SNAPSHOT - provided - - - ``` - -- **Statistics:** - ![Graph](https://bstats.org/signatures/bukkit/AuthMe.svg) - -## Requirements - -##### Compiling requirements: ->- JDK 11 (JDK 17 is recommended) ->- Maven ->- Git/Github (Optional) - -##### How to compile the project: ->- Clone the project with Git/GitHub ->- Execute command "mvn clean package" - -##### Running requirements: ->- Java 8 (Java 17 is recommended) ->- Paper or Spigot (1.8.X and up)
- (In case you use Thermos, Cauldron or similar, you have to update the SpecialSource library to support Java 8 plugins. - HowTo: https://github.com/games647/FastLogin/issues/111#issuecomment-272331347) ->- ProtocolLib (optional, required by some features) - -## Credits - -##### Contributors: -Team members: developers, translators - -Credits for the old version of the plugin: d4rkwarriors, fabe1337, Whoami2 and pomo4ka - -Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex - -##### GeoIP License: -This product uses data from the GeoLite API created by MaxMind, available at https://www.maxmind.com + + + Star History Chart + diff --git a/docs/config.md b/docs/config.md index 1c86eaf7..d5f46275 100644 --- a/docs/config.md +++ b/docs/config.md @@ -9,7 +9,7 @@ the generated config.yml file. ```yml DataSource: # What type of database do you want to use? - # Valid values: SQLITE, MARIADB, MYSQL, POSTGRESQL + # Valid values: H2, SQLITE, MARIADB, MYSQL, POSTGRESQL backend: SQLITE # Enable the database caching system, should be disabled on bungeecord environments # or when a website integration is being used. @@ -304,13 +304,6 @@ settings: forceKickAfterRegister: false # Does AuthMe need to enforce a /login after a successful registration? forceLoginAfterRegister: false - # Enable to display the welcome message (welcome.txt) after a login - # You can use colors in this welcome.txt + some replaced strings: - # {PLAYER}: player name, {ONLINE}: display number of online players, - # {MAXPLAYERS}: display server slots, {IP}: player ip, {LOGINS}: number of players logged, - # {WORLD}: player current world, {SERVER}: server name - # {VERSION}: get current bukkit version, {COUNTRY}: player country - useWelcomeMessage: true # Broadcast the welcome message to the server or only to the player? # set true for server or false for player broadcastWelcomeMessage: false diff --git a/pom.xml b/pom.xml index 85442ead..f05a73c8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,10 +6,10 @@ fr.xephi authme - 5.6.0-SNAPSHOT + 5.6.0-FORK - AuthMeReloaded - The first authentication plugin for the Bukkit API! + AuthMeReReloaded + Fork of the first authentication plugin for the Bukkit API! 2013 https://github.com/AuthMe/AuthMeReloaded @@ -47,7 +47,7 @@ - The GNU General Public Licence version 3 (GPLv3) + The GNU Public Licence version 3 (GPLv3) https://www.gnu.org/licenses/gpl-3.0.html repo @@ -67,11 +67,11 @@ 3.6.3 - 1.19.2-R0.1-SNAPSHOT + 1.20.2-R0.1-SNAPSHOT AuthMe - CUSTOM + 28 ${project.version}-b${project.buildNumber} ${project.outputName}-${project.version} @@ -189,19 +189,24 @@ org.apache.maven.plugins maven-clean-plugin - 3.2.0 + 3.3.2 org.apache.maven.plugins maven-resources-plugin - 3.3.0 + 3.3.1 + + + mmdb + + org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.12.1 ${java.source} ${java.target} @@ -272,7 +277,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.0 ${project.finalNameBase} @@ -289,7 +294,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.4.1 + 3.5.2 shaded-jar @@ -319,7 +324,7 @@ shade - ${project.finalNameBase}-legacy + ${project.finalNameBase}-Spigot-Universal com.google.common @@ -341,6 +346,10 @@ com.google.gson fr.xephi.authme.libs.com.google.gson + + org.h2 + fr.xephi.authme.libs.org.h2 + @@ -435,6 +444,14 @@ com.google.protobuf fr.xephi.authme.libs.com.google.protobuf + + io.netty + fr.xephi.authme.libs.io.netty + + + org.apache.commons.validator + fr.xephi.authme.libs.org.apache.commons.validator + @@ -502,6 +519,15 @@ + + opencollab-snapshot-main + https://repo.opencollab.dev/main/ + + + opencollab-maven-snapshots + https://repo.opencollab.dev/maven-snapshots/ + + apache-snapshots @@ -593,11 +619,43 @@ true + + + + devmart-other + https://nexuslite.gcnt.net/repos/other/ + + + + opencollab-snapshot + https://repo.opencollab.dev/maven-snapshots/ + + false + + + true + + + + jitpack + https://jitpack.io/ + + true + + + false + + - + + org.geysermc.floodgate + api + 2.2.2-SNAPSHOT + provided + ch.jalu @@ -641,7 +699,7 @@ org.apache.commons commons-email - 1.6-SNAPSHOT + 1.6.0 true @@ -649,7 +707,7 @@ org.apache.logging.log4j log4j-core - 2.8.1 + 2.20.0 provided @@ -692,7 +750,7 @@ org.mariadb.jdbc mariadb-java-client - 3.3.0 + 3.3.3 true @@ -737,7 +795,7 @@ com.google.guava guava - 31.0.1-jre + 31.1-jre true @@ -750,17 +808,15 @@ com.google.code.gson gson - 2.8.9 + 2.10.1 true - - ch.jalu configme - 1.3.0 + 1.3.1 true @@ -770,11 +826,11 @@ - + org.bstats bstats-bukkit - 3.0.0 + 3.0.2 true @@ -782,7 +838,7 @@ com.comphenix.protocol ProtocolLib - 4.8.0 + 5.1.0 provided @@ -826,6 +882,20 @@ + + + + + + + + + + + + + + org.tyrannyofheaven.bukkit @@ -1027,7 +1097,7 @@ org.mockito mockito-core test - 4.8.1 + 5.2.0 hamcrest-core @@ -1048,14 +1118,14 @@ org.xerial sqlite-jdbc - 3.44.0.0 + 3.45.1.0 test com.h2database h2 - 2.2.220 - test + 2.2.224 + compile diff --git a/samples/NewConfig.yml b/samples/NewConfig.yml index c0b392c4..08a8d225 100644 --- a/samples/NewConfig.yml +++ b/samples/NewConfig.yml @@ -412,12 +412,6 @@ settings: forceRegisterCommands: [] # Force these commands after /register as a server console, without any '/', use %p for replace with player name forceRegisterCommandsAsConsole: [] - # Do we need to display the welcome message (welcome.txt) after a register or a login? - # You can use colors in this welcome.txt + some replaced strings : - # {PLAYER} : player name, {ONLINE} : display number of online players, {MAXPLAYERS} : display server slots, - # {IP} : player ip, {LOGINS} : number of players logged, {WORLD} : player current world, {SERVER} : server name - # {VERSION} : get current bukkit version, {COUNTRY} : player country - useWelcomeMessage: true # 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 # Do we need to delay the join/leave message to be displayed only when the player is authenticated ? diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 045d1ee9..8bf05070 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -2,7 +2,6 @@ package fr.xephi.authme; import ch.jalu.injector.Injector; import ch.jalu.injector.InjectorBuilder; -import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.api.v3.AuthMeApi; import fr.xephi.authme.command.CommandHandler; import fr.xephi.authme.datasource.DataSource; @@ -12,13 +11,20 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver; import fr.xephi.authme.initialization.OnStartupTasks; import fr.xephi.authme.initialization.SettingsProvider; import fr.xephi.authme.initialization.TaskCloser; +import fr.xephi.authme.listener.AdvancedShulkerFixListener; +import fr.xephi.authme.listener.BedrockAutoLoginListener; import fr.xephi.authme.listener.BlockListener; +import fr.xephi.authme.listener.DoubleLoginFixListener; import fr.xephi.authme.listener.EntityListener; +import fr.xephi.authme.listener.GuiCaptchaHandler; +import fr.xephi.authme.listener.LoginLocationFixListener; import fr.xephi.authme.listener.PlayerListener; import fr.xephi.authme.listener.PlayerListener111; import fr.xephi.authme.listener.PlayerListener19; import fr.xephi.authme.listener.PlayerListener19Spigot; +import fr.xephi.authme.listener.PlayerListenerHigherThan18; import fr.xephi.authme.listener.ServerListener; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.service.BackupService; @@ -28,6 +34,8 @@ import fr.xephi.authme.service.bungeecord.BungeeReceiver; import fr.xephi.authme.service.yaml.YamlParseException; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SettingsWarner; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.task.CleanupTask; import fr.xephi.authme.task.purge.PurgeService; @@ -35,14 +43,21 @@ import fr.xephi.authme.util.ExceptionUtils; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.java.JavaPluginLoader; import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; +import javax.inject.Inject; import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Scanner; import java.util.function.Consumer; +import java.util.logging.Level; import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE; import static fr.xephi.authme.util.Utils.isClassLoaded; @@ -58,17 +73,19 @@ public class AuthMe extends JavaPlugin { private static final int CLEANUP_INTERVAL = 5 * TICKS_PER_MINUTE; // Version and build number values - private static String pluginVersion = "N/D"; - private static String pluginBuildNumber = "Unknown"; - + private static String pluginVersion = "5.6.0-Fork"; + private static final String pluginBuild = "b"; + private static String pluginBuildNumber = "42"; // Private instances + private EmailService emailService; private CommandHandler commandHandler; - private Settings settings; + @Inject + public static Settings settings; private DataSource database; private BukkitService bukkitService; private Injector injector; private BackupService backupService; - private ConsoleLogger logger; + public static ConsoleLogger logger; /** * Constructor. @@ -76,14 +93,16 @@ public class AuthMe extends JavaPlugin { public AuthMe() { } - /* - * Constructor for unit testing. + /** + * Get the plugin's build + * + * @return The plugin's build */ - @VisibleForTesting - AuthMe(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { - super(loader, description, dataFolder, file); + public static String getPluginBuild() { + return pluginBuild; } + /** * Get the plugin's name. * @@ -111,6 +130,8 @@ public class AuthMe extends JavaPlugin { return pluginBuildNumber; } + + /** * Method called when the server enables the plugin. */ @@ -122,6 +143,8 @@ public class AuthMe extends JavaPlugin { // Set the Logger instance and log file path ConsoleLogger.initialize(getLogger(), new File(getDataFolder(), LOG_FILENAME)); logger = ConsoleLoggerFactory.get(AuthMe.class); + logger.info("You are running an unofficial fork version of AuthMe!"); + // Check server version if (!isClassLoaded("org.spigotmc.event.player.PlayerSpawnLocationEvent") @@ -162,26 +185,42 @@ public class AuthMe extends JavaPlugin { // Schedule clean up task CleanupTask cleanupTask = injector.getSingleton(CleanupTask.class); cleanupTask.runTaskTimerAsynchronously(this, CLEANUP_INTERVAL, CLEANUP_INTERVAL); - // Do a backup on start backupService.doBackup(BackupService.BackupCause.START); - // Set up Metrics OnStartupTasks.sendMetrics(this, settings); - + if (settings.getProperty(SecuritySettings.SHOW_STARTUP_BANNER)) { + logger.info("\n" + " ___ __ __ __ ___ \n" + + " / | __ __/ /_/ /_ / |/ /__ \n" + + " / /| |/ / / / __/ __ \\/ /|_/ / _ \\\n" + + " / ___ / /_/ / /_/ / / / / / / __/\n" + + "/_/ |_\\__,_/\\__/_/ /_/_/ /_/\\___/ \n" + + " "); + } // Successful message - logger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber() + " successfully enabled!"); - + //detect server brand with classloader + checkServerType(); + logger.info("AuthMeReReloaded is enabled successfully!"); // Purge on start if enabled PurgeService purgeService = injector.getSingleton(PurgeService.class); purgeService.runAutoPurge(); + // 注册玩家加入事件监听 +// register3rdPartyListeners(); + logger.info("GitHub: https://github.com/HaHaWTH/AuthMeReReloaded/"); + if (settings.getProperty(SecuritySettings.CHECK_FOR_UPDATES)) { + checkForUpdates(); + } } + + //Migrated + /** * Load the version and build number of the plugin from the description file. * * @param versionRaw the version as given by the plugin description file */ + private static void loadPluginInfo(String versionRaw) { int index = versionRaw.lastIndexOf("-"); if (index != -1) { @@ -245,6 +284,7 @@ public class AuthMe extends JavaPlugin { database = injector.getSingleton(DataSource.class); bukkitService = injector.getSingleton(BukkitService.class); commandHandler = injector.getSingleton(CommandHandler.class); + emailService = injector.getSingleton(EmailService.class); backupService = injector.getSingleton(BackupService.class); // Trigger instantiation (class not used elsewhere) @@ -269,10 +309,17 @@ public class AuthMe extends JavaPlugin { pluginManager.registerEvents(injector.getSingleton(EntityListener.class), this); pluginManager.registerEvents(injector.getSingleton(ServerListener.class), this); - // Try to register 1.9 player listeners - if (isClassLoaded("org.bukkit.event.player.PlayerSwapHandItemsEvent")) { + + // Try to register 1.8+ player listeners + if (isClassLoaded("org.bukkit.event.entity.EntityPickupItemEvent") && isClassLoaded("org.bukkit.event.player.PlayerSwapHandItemsEvent")) { + pluginManager.registerEvents(injector.getSingleton(PlayerListenerHigherThan18.class), this); + } else if (isClassLoaded("org.bukkit.event.player.PlayerSwapHandItemsEvent")) { pluginManager.registerEvents(injector.getSingleton(PlayerListener19.class), this); } +// Try to register 1.9 player listeners(Moved to else-if) +// if (isClassLoaded("org.bukkit.event.player.PlayerSwapHandItemsEvent")) { +// pluginManager.registerEvents(injector.getSingleton(PlayerListener19.class), this); +// } // Try to register 1.9 spigot player listeners if (isClassLoaded("org.spigotmc.event.player.PlayerSpawnLocationEvent")) { @@ -283,6 +330,31 @@ public class AuthMe extends JavaPlugin { if (isClassLoaded("org.bukkit.event.entity.EntityAirChangeEvent")) { pluginManager.registerEvents(injector.getSingleton(PlayerListener111.class), this); } + + //Register 3rd party listeners + if (settings.getProperty(SecuritySettings.GUI_CAPTCHA) && getServer().getPluginManager().getPlugin("ProtocolLib") != null) { + pluginManager.registerEvents(injector.getSingleton(GuiCaptchaHandler.class), this); + logger.info("(Beta)GUICaptcha is enabled successfully!"); + logger.info("These features are still in early development, if you encountered any problem, please report."); + } else if (settings.getProperty(SecuritySettings.GUI_CAPTCHA) && getServer().getPluginManager().getPlugin("ProtocolLib") == null) { + logger.warning("ProtocolLib is not loaded, can't enable GUI Captcha."); + } + if (settings.getProperty(SecuritySettings.FORCE_LOGIN_BEDROCK) && settings.getProperty(HooksSettings.HOOK_FLOODGATE_PLAYER) && getServer().getPluginManager().getPlugin("floodgate") != null) { + pluginManager.registerEvents(injector.getSingleton(BedrockAutoLoginListener.class), this); + } else if (settings.getProperty(SecuritySettings.FORCE_LOGIN_BEDROCK) && (!settings.getProperty(HooksSettings.HOOK_FLOODGATE_PLAYER) || getServer().getPluginManager().getPlugin("floodgate") == null)) { + logger.warning("Failed to enable BedrockAutoLogin, ensure hookFloodgate: true and floodgate is loaded."); + } + if (settings.getProperty(SecuritySettings.LOGIN_LOC_FIX_SUB_UNDERGROUND) || settings.getProperty(SecuritySettings.LOGIN_LOC_FIX_SUB_PORTAL)) { + pluginManager.registerEvents(injector.getSingleton(LoginLocationFixListener.class), this); + } + if (settings.getProperty(SecuritySettings.ANTI_GHOST_PLAYERS)) { + pluginManager.registerEvents(injector.getSingleton(DoubleLoginFixListener.class), this); + } + if (settings.getProperty(SecuritySettings.ADVANCED_SHULKER_FIX) && !isClassLoaded("org.bukkit.event.player.PlayerCommandSendEvent")) { + pluginManager.registerEvents(injector.getSingleton(AdvancedShulkerFixListener.class), this); + } else if (settings.getProperty(SecuritySettings.ADVANCED_SHULKER_FIX) && isClassLoaded("org.bukkit.event.player.PlayerCommandSendEvent")) { + logger.warning("You are running an 1.13+ minecraft server, advancedShulkerFix won't enable."); + } } /** @@ -307,6 +379,11 @@ public class AuthMe extends JavaPlugin { if (onShutdownPlayerSaver != null) { onShutdownPlayerSaver.saveAllPlayers(); } + if (settings.getProperty(EmailSettings.SHUTDOWN_MAIL)){ + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy'.'MM'.'dd'.' HH:mm:ss"); + Date date = new Date(System.currentTimeMillis()); + emailService.sendShutDown(settings.getProperty(EmailSettings.SHUTDOWN_MAIL_ADDRESS),dateFormat.format(date)); + } // Do backup on stop if enabled if (backupService != null) { @@ -318,10 +395,76 @@ public class AuthMe extends JavaPlugin { // Disabled correctly Consumer infoLogMethod = logger == null ? getLogger()::info : logger::info; - infoLogMethod.accept("AuthMe " + this.getDescription().getVersion() + " disabled!"); + infoLogMethod.accept("AuthMe " + this.getDescription().getVersion() + " is unloaded successfully!"); ConsoleLogger.closeFileWriter(); } + private static final String owner = "HaHaWTH"; +// private static final String owner_gitee = "Shixuehan114514"; + private static final String repo = "AuthMeReReloaded"; + + private void checkForUpdates() { + logger.info("Checking for updates..."); + bukkitService.runTaskAsynchronously(() -> { + try { + // 从南通集线器获取最新版本号 + URL url = new URL("https://api.github.com/repos/" + owner + "/" + repo + "/releases/latest"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(10000); // 设置连接超时为10秒 + conn.setReadTimeout(10000); // 设置读取超时为10秒 + Scanner scanner = new Scanner(conn.getInputStream()); + String response = scanner.useDelimiter("\\Z").next(); + scanner.close(); + + // 处理JSON响应 + String latestVersion = response.substring(response.indexOf("tag_name") + 11); + latestVersion = latestVersion.substring(0, latestVersion.indexOf("\"")); + if (isUpdateAvailable(latestVersion)) { + String message = "New version available! Latest:" + latestVersion + " Current:" + pluginBuild + pluginBuildNumber; + getLogger().log(Level.WARNING, message); + getLogger().log(Level.WARNING, "Download from here: https://github.com/HaHaWTH/AuthMeReReloaded/releases/latest"); + } else { + getLogger().log(Level.INFO, "You are running the latest version."); + } + } catch (IOException ignored) { + } + }); + } + private boolean isUpdateAvailable(String latestVersion) { + // Extract the first character and the remaining digits from the version string + char latestChar = latestVersion.charAt(0); + int latestNumber = Integer.parseInt(latestVersion.substring(1)); + + char currentChar = pluginBuild.charAt(0); + int currentNumber = Integer.parseInt(pluginBuildNumber); + + // Compare the characters first + if (latestChar > currentChar) { + return true; + } else if (latestChar < currentChar) { + return false; + } else { + // If the characters are the same, compare the numbers + return latestNumber > currentNumber; + } + } + + + private void checkServerType() { + if (isClassLoaded("com.destroystokyo.paper.PaperConfig")) { + logger.info("AuthMeReReloaded is running on Paper"); + } else if (isClassLoaded("catserver.server.CatServerConfig")) { + logger.info("AuthMeReReloaded is running on CatServer"); + } else if (isClassLoaded("org.spigotmc.SpigotConfig")) { + logger.info("AuthMeReReloaded is running on Spigot"); + } else if (isClassLoaded("org.bukkit.craftbukkit.CraftServer")) { + logger.info("AuthMeReReloaded is running on Bukkit"); + } else { + logger.info("AuthMeReReloaded is running on Unknown*"); + } + } + + /** * Handle Bukkit commands. * @@ -332,8 +475,8 @@ public class AuthMe extends JavaPlugin { * @return True if the command was executed, false otherwise. */ @Override - public boolean onCommand(CommandSender sender, Command cmd, - String commandLabel, String[] args) { + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, + @NotNull String commandLabel, String[] args) { // Make sure the command handler has been initialized if (commandHandler == null) { getLogger().severe("AuthMe command handler is not available"); diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java index 88798ced..1b1d6788 100644 --- a/src/main/java/fr/xephi/authme/ConsoleLogger.java +++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java @@ -142,7 +142,7 @@ public final class ConsoleLogger { public void fine(String message) { if (logLevel.includes(LogLevel.FINE)) { logger.info(message); - writeLog("[FINE] " + message); + writeLog("[INFO:FINE] " + message); } } @@ -215,7 +215,7 @@ public final class ConsoleLogger { } private void logAndWriteWithDebugPrefix(String message) { - String debugMessage = "[DEBUG] " + message; + String debugMessage = "[INFO:DEBUG] " + message; logger.info(debugMessage); writeLog(debugMessage); } 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 b035b63e..1645a560 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 @@ -11,11 +11,12 @@ import fr.xephi.authme.datasource.converter.LoginSecurityConverter; import fr.xephi.authme.datasource.converter.MySqlToSqlite; import fr.xephi.authme.datasource.converter.RakamakConverter; import fr.xephi.authme.datasource.converter.RoyalAuthConverter; +import fr.xephi.authme.datasource.converter.SqliteToH2; import fr.xephi.authme.datasource.converter.SqliteToSql; import fr.xephi.authme.datasource.converter.VAuthConverter; import fr.xephi.authme.datasource.converter.XAuthConverter; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import org.bukkit.command.CommandSender; @@ -89,6 +90,7 @@ public class ConverterCommand implements ExecutableCommand { .put("vauth", VAuthConverter.class) .put("sqlitetosql", SqliteToSql.class) .put("mysqltosqlite", MySqlToSqlite.class) + .put("sqlitetoh2", SqliteToH2.class) .put("loginsecurity", LoginSecurityConverter.class) .build(); } 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 acfc59be..7bd7d7cc 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,7 +1,10 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.TeleportUtils; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -11,16 +14,22 @@ import java.util.List; * Teleports the player to the first spawn. */ public class FirstSpawnCommand extends PlayerCommand { - + @Inject + private Settings settings; @Inject private SpawnLoader spawnLoader; - @Override 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()); + //String name= player.getName(); + if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + TeleportUtils.teleport(player, spawnLoader.getFirstSpawn()); + } else { + player.teleport(spawnLoader.getFirstSpawn()); + } + //player.teleport(spawnLoader.getFirstSpawn()); } } } 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 eaa56a41..a189b964 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 @@ -4,8 +4,8 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.BukkitService; 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 84de195c..2956a39f 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 @@ -7,8 +7,8 @@ import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.SettingsDependent; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SettingsWarner; 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 5c6abed5..2f7e8a7c 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,6 +3,8 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.DatabaseSettings; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -17,6 +19,8 @@ public class VersionCommand implements ExecutableCommand { @Inject private BukkitService bukkitService; + @Inject + private Settings settings; @Override public void executeCommand(CommandSender sender, List arguments) { @@ -24,6 +28,7 @@ public class VersionCommand implements ExecutableCommand { sender.sendMessage(ChatColor.GOLD + "==========[ " + AuthMe.getPluginName() + " ABOUT ]=========="); sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName() + " v" + AuthMe.getPluginVersion() + ChatColor.GRAY + " (build: " + AuthMe.getPluginBuildNumber() + ")"); + sender.sendMessage(ChatColor.GOLD + "Database Implementation: " + ChatColor.WHITE + settings.getProperty(DatabaseSettings.BACKEND).toString()); sender.sendMessage(ChatColor.GOLD + "Authors:"); Collection onlinePlayers = bukkitService.getOnlinePlayers(); printDeveloper(sender, "Gabriele C.", "sgdc3", "Project manager, Contributor", onlinePlayers); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java index f393db72..388a18af 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java @@ -3,8 +3,8 @@ package fr.xephi.authme.command.executable.authme.debug; import ch.jalu.datasourcecolumns.data.DataSourceValue; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.mail.SendMailSsl; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.permission.DebugSectionPermissions; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.util.StringUtils; 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 72ff55bc..83a0e5b2 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 @@ -42,7 +42,6 @@ public class ChangePasswordCommand extends PlayerCommand { commonService.send(player, MessageKey.NOT_LOGGED_IN); return; } - // Check if the user has been verified or not if (codeManager.isVerificationRequired(player)) { codeManager.codeExistOrGenerateNew(name); diff --git a/src/main/java/fr/xephi/authme/command/executable/email/EmailSetPasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/EmailSetPasswordCommand.java index 734fc064..f3979adc 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/EmailSetPasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/EmailSetPasswordCommand.java @@ -3,8 +3,8 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.CommonService; 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 5fa7e27f..ebb71133 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 @@ -5,9 +5,9 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PasswordRecoveryService; 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 922e1104..d2185491 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 @@ -3,9 +3,10 @@ package fr.xephi.authme.command.executable.register; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.captcha.RegistrationCaptchaManager; -import fr.xephi.authme.output.ConsoleLoggerFactory; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.RegisterSecondaryArgument; import fr.xephi.authme.process.register.RegistrationType; @@ -23,6 +24,8 @@ import org.bukkit.entity.Player; import javax.inject.Inject; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; import static fr.xephi.authme.process.register.RegisterSecondaryArgument.CONFIRMATION; import static fr.xephi.authme.process.register.RegisterSecondaryArgument.EMAIL_MANDATORY; @@ -43,6 +46,9 @@ public class RegisterCommand extends PlayerCommand { @Inject private CommonService commonService; + @Inject + private DataSource dataSource; + @Inject private EmailService emailService; @@ -169,6 +175,20 @@ public class RegisterCommand extends PlayerCommand { } else if (isSecondArgValidForEmailRegistration(player, arguments)) { management.performRegister(RegistrationMethod.EMAIL_REGISTRATION, EmailRegisterParams.of(player, email)); + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + if (dataSource.getAuth(player.getName()) != null) { + if (dataSource.getAuth(player.getName()).getLastLogin() == null) { + management.performUnregisterByAdmin(null, player.getName(), player); + timer.cancel(); + } + } else { + timer.cancel(); + } + } + }, 600000); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/totp/ConfirmTotpCommand.java b/src/main/java/fr/xephi/authme/command/executable/totp/ConfirmTotpCommand.java index a4cd7857..85d7bc8a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/totp/ConfirmTotpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/totp/ConfirmTotpCommand.java @@ -5,9 +5,9 @@ import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.totp.GenerateTotpService; import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult; import org.bukkit.entity.Player; diff --git a/src/main/java/fr/xephi/authme/command/executable/totp/RemoveTotpCommand.java b/src/main/java/fr/xephi/authme/command/executable/totp/RemoveTotpCommand.java index bc3cdd3d..649f13e4 100644 --- a/src/main/java/fr/xephi/authme/command/executable/totp/RemoveTotpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/totp/RemoveTotpCommand.java @@ -5,9 +5,9 @@ import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.totp.TotpAuthenticator; import org.bukkit.entity.Player; diff --git a/src/main/java/fr/xephi/authme/command/executable/totp/TotpCodeCommand.java b/src/main/java/fr/xephi/authme/command/executable/totp/TotpCodeCommand.java index 1ac48374..760b83ec 100644 --- a/src/main/java/fr/xephi/authme/command/executable/totp/TotpCodeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/totp/TotpCodeCommand.java @@ -8,9 +8,9 @@ import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboPlayerState; import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.security.totp.TotpAuthenticator; import org.bukkit.entity.Player; diff --git a/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java b/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java index ac8ddb86..dc28c95f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/verification/VerificationCommand.java @@ -3,8 +3,8 @@ package fr.xephi.authme.command.executable.verification; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.VerificationCodeManager; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.service.CommonService; import org.bukkit.entity.Player; diff --git a/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java b/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java index 0a8ff061..ad1b778d 100644 --- a/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java +++ b/src/main/java/fr/xephi/authme/data/VerificationCodeManager.java @@ -15,6 +15,8 @@ import fr.xephi.authme.util.expiring.ExpiringMap; import org.bukkit.entity.Player; import javax.inject.Inject; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -132,12 +134,14 @@ public class VerificationCodeManager implements SettingsDependent, HasCleanup { */ private void generateCode(String name) { DataSourceValue emailResult = dataSource.getEmail(name); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy'年'MM'月'dd'日' HH:mm:ss"); + Date date = new Date(System.currentTimeMillis()); if (emailResult.rowExists()) { final String email = emailResult.getValue(); if (!Utils.isEmailEmpty(email)) { String code = RandomStringUtils.generateNum(6); // 6 digits code verificationCodes.put(name.toLowerCase(Locale.ROOT), code); - emailService.sendVerificationMail(name, email, code); + emailService.sendVerificationMail(name, email, code, dateFormat.format(date)); } } } diff --git a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java index 841542a5..f36faba7 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java @@ -4,6 +4,7 @@ package fr.xephi.authme.datasource; * DataSource type. */ public enum DataSourceType { + H2, MYSQL, diff --git a/src/main/java/fr/xephi/authme/datasource/H2.java b/src/main/java/fr/xephi/authme/datasource/H2.java new file mode 100644 index 00000000..94b28f58 --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/H2.java @@ -0,0 +1,422 @@ +package fr.xephi.authme.datasource; + +import com.google.common.annotations.VisibleForTesting; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler; +import fr.xephi.authme.output.ConsoleLoggerFactory; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.DatabaseSettings; + +import java.io.File; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import static fr.xephi.authme.datasource.SqlDataSourceUtils.getNullableLong; +import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException; + +/** + * H2 data source. + */ +@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore +public class H2 extends AbstractSqlDataSource { + + private final ConsoleLogger logger = ConsoleLoggerFactory.get(H2.class); + private final Settings settings; + private final File dataFolder; + private final String database; + private final String tableName; + private final Columns col; + private Connection con; + + /** + * Constructor for H2. + * + * @param settings The settings instance + * @param dataFolder The data folder + * @throws SQLException when initialization of a SQL datasource failed + */ + public H2(Settings settings, File dataFolder) throws SQLException { + this.settings = settings; + this.dataFolder = dataFolder; + this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); + this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); + this.col = new Columns(settings); + + try { + this.connect(); + this.setup(); + } catch (Exception ex) { + logger.logException("Error during H2 initialization:", ex); + throw ex; + } + } + + @VisibleForTesting + H2(Settings settings, File dataFolder, Connection connection) { + this.settings = settings; + this.dataFolder = dataFolder; + this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); + this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); + this.col = new Columns(settings); + this.con = connection; + this.columnsHandler = AuthMeColumnsHandler.createForH2(con, settings); + } + + /** + * Initializes the connection to the H2 database. + * + * @throws SQLException when an SQL error occurs while connecting + */ + protected void connect() throws SQLException { + try { + Class.forName("org.h2.Driver"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Failed to load H2 JDBC class", e); + } + + logger.debug("H2 driver loaded"); + this.con = DriverManager.getConnection(this.getJdbcUrl(this.dataFolder.getAbsolutePath(), "", this.database)); + this.columnsHandler = AuthMeColumnsHandler.createForSqlite(con, settings); + } + + /** + * Creates the table if necessary, or adds any missing columns to the table. + * + * @throws SQLException when an SQL error occurs while initializing the database + */ + @VisibleForTesting + @SuppressWarnings("checkstyle:CyclomaticComplexity") + protected void setup() throws SQLException { + try (Statement st = con.createStatement()) { + // Note: cannot add unique fields later on in SQLite, so we add it on initialization + st.executeUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " (" + + col.ID + " INTEGER AUTO_INCREMENT, " + + col.NAME + " VARCHAR(255) NOT NULL UNIQUE, " + + "CONSTRAINT table_const_prim PRIMARY KEY (" + col.ID + "));"); + + DatabaseMetaData md = con.getMetaData(); + + if (isColumnMissing(md, col.REAL_NAME)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + + col.REAL_NAME + " VARCHAR(255) NOT NULL DEFAULT 'Player';"); + } + + if (isColumnMissing(md, col.PASSWORD)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.PASSWORD + " VARCHAR(255) NOT NULL DEFAULT '';"); + } + + if (!col.SALT.isEmpty() && isColumnMissing(md, col.SALT)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + col.SALT + " VARCHAR(255);"); + } + + if (isColumnMissing(md, col.LAST_IP)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.LAST_IP + " VARCHAR(40);"); + } + + if (isColumnMissing(md, col.LAST_LOGIN)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.LAST_LOGIN + " BIGINT;"); + } + + if (isColumnMissing(md, col.REGISTRATION_IP)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.REGISTRATION_IP + " VARCHAR(40);"); + } + + if (isColumnMissing(md, col.REGISTRATION_DATE)) { + addRegistrationDateColumn(st); + } + + if (isColumnMissing(md, col.LASTLOC_X)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + col.LASTLOC_X + + " DOUBLE NOT NULL DEFAULT '0.0';"); + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + col.LASTLOC_Y + + " DOUBLE NOT NULL DEFAULT '0.0';"); + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + col.LASTLOC_Z + + " DOUBLE NOT NULL DEFAULT '0.0';"); + } + + if (isColumnMissing(md, col.LASTLOC_WORLD)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.LASTLOC_WORLD + " VARCHAR(255) NOT NULL DEFAULT 'world';"); + } + + if (isColumnMissing(md, col.LASTLOC_YAW)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + + col.LASTLOC_YAW + " FLOAT;"); + } + + if (isColumnMissing(md, col.LASTLOC_PITCH)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + + col.LASTLOC_PITCH + " FLOAT;"); + } + + if (isColumnMissing(md, col.EMAIL)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.EMAIL + " VARCHAR_IGNORECASE(255);"); + } + + if (isColumnMissing(md, col.IS_LOGGED)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.IS_LOGGED + " INT NOT NULL DEFAULT '0';"); + } + + if (isColumnMissing(md, col.HAS_SESSION)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';"); + } + + if (isColumnMissing(md, col.TOTP_KEY)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.TOTP_KEY + " VARCHAR(32);"); + } + + if (!col.PLAYER_UUID.isEmpty() && isColumnMissing(md, col.PLAYER_UUID)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.PLAYER_UUID + " VARCHAR(36)"); + } + } + logger.info("H2 Setup finished"); + } + + /** + * Checks if a column is missing in the specified table. + * @param columnName the name of the column to look for + * @return true if the column is missing, false if it exists + * @throws SQLException if an error occurs while executing SQL or accessing the result set + * @deprecated Not work in H2, it always returns true + */ + @Deprecated + private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException { + String query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?"; +// try (PreparedStatement preparedStatement = con.prepareStatement(query)) { +// preparedStatement.setString(1, tableName); +// preparedStatement.setString(2, columnName.toUpperCase()); +// try (ResultSet rs = preparedStatement.executeQuery()) { +// return !rs.next(); +// } +// } + return true; + } + + + @Override + public void reload() { + close(con); + try { + this.connect(); + this.setup(); + } catch (SQLException ex) { + logger.logException("Error while reloading H2:", ex); + } + } + + @Override + public PlayerAuth getAuth(String user) { + String sql = "SELECT * FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=LOWER(?);"; + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user); + try (ResultSet rs = pst.executeQuery()) { + if (rs.next()) { + return buildAuthFromResultSet(rs); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return null; + } + + @Override + public Set getRecordsToPurge(long until) { + Set list = new HashSet<>(); + String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE MAX(" + + " COALESCE(" + col.LAST_LOGIN + ", 0)," + + " COALESCE(" + col.REGISTRATION_DATE + ", 0)" + + ") < ?;"; + try (PreparedStatement selectPst = con.prepareStatement(select)) { + selectPst.setLong(1, until); + try (ResultSet rs = selectPst.executeQuery()) { + while (rs.next()) { + list.add(rs.getString(col.NAME)); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + + return list; + } + + @Override + public void purgeRecords(Collection toPurge) { + String delete = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (PreparedStatement deletePst = con.prepareStatement(delete)) { + for (String name : toPurge) { + deletePst.setString(1, name.toLowerCase(Locale.ROOT)); + deletePst.executeUpdate(); + } + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public boolean removeAuth(String user) { + String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user.toLowerCase(Locale.ROOT)); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public void closeConnection() { + try { + if (con != null && !con.isClosed()) { + con.close(); + } + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public DataSourceType getType() { + return DataSourceType.H2; + } + + @Override + public List getAllAuths() { + List auths = new ArrayList<>(); + String sql = "SELECT * FROM " + tableName + ";"; + try (PreparedStatement pst = con.prepareStatement(sql); ResultSet rs = pst.executeQuery()) { + while (rs.next()) { + PlayerAuth auth = buildAuthFromResultSet(rs); + auths.add(auth); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return auths; + } + + @Override + public List getLoggedPlayersWithEmptyMail() { + List players = new ArrayList<>(); + String sql = "SELECT " + col.REAL_NAME + " FROM " + tableName + " WHERE " + col.IS_LOGGED + " = 1" + + " AND (" + col.EMAIL + " = 'your@email.com' OR " + col.EMAIL + " IS NULL);"; + try (Statement st = con.createStatement(); ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + players.add(rs.getString(1)); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return players; + } + + @Override + public List getRecentlyLoggedInPlayers() { + List players = new ArrayList<>(); + String sql = "SELECT * FROM " + tableName + " ORDER BY " + col.LAST_LOGIN + " DESC LIMIT 10;"; + try (Statement st = con.createStatement(); ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + players.add(buildAuthFromResultSet(rs)); + } + } catch (SQLException e) { + logSqlException(e); + } + return players; + } + + + @Override + public boolean setTotpKey(String user, String totpKey) { + String sql = "UPDATE " + tableName + " SET " + col.TOTP_KEY + " = ? WHERE " + col.NAME + " = ?"; + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, totpKey); + pst.setString(2, user.toLowerCase(Locale.ROOT)); + pst.executeUpdate(); + return true; + } catch (SQLException e) { + logSqlException(e); + } + return false; + } + + private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { + String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null; + + return PlayerAuth.builder() + .name(row.getString(col.NAME)) + .email(row.getString(col.EMAIL)) + .realName(row.getString(col.REAL_NAME)) + .password(row.getString(col.PASSWORD), salt) + .totpKey(row.getString(col.TOTP_KEY)) + .lastLogin(getNullableLong(row, col.LAST_LOGIN)) + .lastIp(row.getString(col.LAST_IP)) + .registrationDate(row.getLong(col.REGISTRATION_DATE)) + .registrationIp(row.getString(col.REGISTRATION_IP)) + .locX(row.getDouble(col.LASTLOC_X)) + .locY(row.getDouble(col.LASTLOC_Y)) + .locZ(row.getDouble(col.LASTLOC_Z)) + .locWorld(row.getString(col.LASTLOC_WORLD)) + .locYaw(row.getFloat(col.LASTLOC_YAW)) + .locPitch(row.getFloat(col.LASTLOC_PITCH)) + .build(); + } + + /** + * Creates the column for registration date and sets all entries to the current timestamp. + * We do so in order to avoid issues with purging, where entries with 0 / NULL might get + * purged immediately on startup otherwise. + * + * @param st Statement object to the database + */ + private void addRegistrationDateColumn(Statement st) throws SQLException { + int affect = st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN IF NOT EXISTS " + col.REGISTRATION_DATE + " BIGINT NOT NULL DEFAULT '0';"); + if (affect > 0) { + long currentTimestamp = System.currentTimeMillis(); + int updatedRows = st.executeUpdate(String.format("UPDATE %s SET %s = %d;", + tableName, col.REGISTRATION_DATE, currentTimestamp)); + logger.info("Created column '" + col.REGISTRATION_DATE + "' and set the current timestamp, " + + currentTimestamp + ", to all " + updatedRows + " rows"); + } + } + + @Override + String getJdbcUrl(String dataPath, String ignored, String database) { + return "jdbc:h2:" + dataPath + File.separator + database; + } + + private static void close(Connection con) { + if (con != null) { + try { + con.close(); + } catch (SQLException ex) { + logSqlException(ex); + } + } + } +} + diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java index 7c4d2e6a..47851a5a 100644 --- a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java @@ -3,8 +3,6 @@ package fr.xephi.authme.datasource.columnshandler; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.settings.properties.DatabaseSettings; -import java.util.UUID; - import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.ColumnOptions.DEFAULT_FOR_NULL; import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.ColumnOptions.OPTIONAL; import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createDouble; diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java index fc172dcf..b910d36e 100644 --- a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java @@ -51,6 +51,26 @@ public final class AuthMeColumnsHandler { return new AuthMeColumnsHandler(sqlColHandler); } + /** + * Creates a column handler for H2. + * + * @param connection the connection to the database + * @param settings plugin settings + * @return created column handler + */ + public static AuthMeColumnsHandler createForH2(Connection connection, Settings settings) { + ColumnContext columnContext = new ColumnContext(settings, false); + String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); + String nameColumn = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME); + + SqlColumnsHandler sqlColHandler = new SqlColumnsHandler<>( + forSingleConnection(connection, tableName, nameColumn, columnContext) + .setPredicateSqlGenerator(new PredicateSqlGenerator<>(columnContext, false)) + ); + return new AuthMeColumnsHandler(sqlColHandler); + } + + /** * Creates a column handler for MySQL. * diff --git a/src/main/java/fr/xephi/authme/datasource/converter/SqliteToH2.java b/src/main/java/fr/xephi/authme/datasource/converter/SqliteToH2.java new file mode 100644 index 00000000..eea2bfca --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/converter/SqliteToH2.java @@ -0,0 +1,33 @@ +package fr.xephi.authme.datasource.converter; + +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.datasource.DataSourceType; +import fr.xephi.authme.datasource.SQLite; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.settings.Settings; + +import javax.inject.Inject; +import java.io.File; +import java.sql.SQLException; + +/** + * Converts SQLite to H2 + * + */ +public class SqliteToH2 extends AbstractDataSourceConverter{ + + private final Settings settings; + private final File dataFolder; + + @Inject + SqliteToH2(Settings settings, DataSource dataSource, @DataFolder File dataFolder) { + super(dataSource, DataSourceType.H2); + this.settings = settings; + this.dataFolder = dataFolder; + } + + @Override + protected SQLite getSource() throws SQLException { + return new SQLite(settings, dataFolder); + } +} diff --git a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java index de5eea46..3593e7ea 100644 --- a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java +++ b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java @@ -5,6 +5,7 @@ import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.CacheDataSource; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; +import fr.xephi.authme.datasource.H2; import fr.xephi.authme.datasource.MariaDB; import fr.xephi.authme.datasource.MySQL; import fr.xephi.authme.datasource.PostgreSqlDataSource; @@ -76,6 +77,9 @@ public class DataSourceProvider implements Provider { case SQLITE: dataSource = new SQLite(settings, dataFolder); break; + case H2: + dataSource = new H2(settings, dataFolder); + break; default: throw new UnsupportedOperationException("Unknown data source type '" + dataSourceType + "'"); } diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index 029c9b60..02f17e95 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -3,10 +3,10 @@ package fr.xephi.authme.initialization; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.output.ConsoleFilter; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; @@ -53,7 +53,7 @@ public class OnStartupTasks { * @param settings the settings */ public static void sendMetrics(AuthMe plugin, Settings settings) { - final Metrics metrics = new Metrics(plugin, 164); + final Metrics metrics = new Metrics(plugin, 18479); metrics.addCustomChart(new SimplePie("messages_language", () -> settings.getProperty(PluginSettings.MESSAGES_LANGUAGE))); @@ -109,6 +109,6 @@ public class OnStartupTasks { } }); } - }, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL)); + }, 1, (long) TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL)); } } diff --git a/src/main/java/fr/xephi/authme/listener/AdvancedShulkerFixListener.java b/src/main/java/fr/xephi/authme/listener/AdvancedShulkerFixListener.java new file mode 100644 index 00000000..64045591 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/AdvancedShulkerFixListener.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.listener; + +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockDispenseEvent; + +//This fix is only for Minecraft 1.13- +public class AdvancedShulkerFixListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onDispenserActivate(BlockDispenseEvent event) { + Block block = event.getBlock(); + + if (block.getY() <= 0 || block.getY() >= block.getWorld().getMaxHeight() - 1) { + event.setCancelled(true); + } + } +} diff --git a/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java b/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java new file mode 100644 index 00000000..16f2f5ea --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/BedrockAutoLoginListener.java @@ -0,0 +1,62 @@ +package fr.xephi.authme.listener; +/* Inspired by DongShaoNB/BedrockPlayerSupport **/ + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.api.v3.AuthMeApi; +import fr.xephi.authme.events.RestoreSessionEvent; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import javax.inject.Inject; +import java.util.UUID; + +import static org.bukkit.Bukkit.getServer; + +public class BedrockAutoLoginListener implements Listener { + private final AuthMeApi authmeApi = AuthMeApi.getInstance(); + @Inject + private BukkitService bukkitService; + @Inject + private AuthMe plugin; + @Inject + private Messages messages; + + @Inject + private Settings settings; + + public BedrockAutoLoginListener() { + } + + private boolean isBedrockPlayer(UUID uuid) { + return settings.getProperty(HooksSettings.HOOK_FLOODGATE_PLAYER) && settings.getProperty(SecuritySettings.FORCE_LOGIN_BEDROCK) && org.geysermc.floodgate.api.FloodgateApi.getInstance().isFloodgateId(uuid) && getServer().getPluginManager().getPlugin("floodgate") != null; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + String name = event.getPlayer().getName(); + UUID uuid = event.getPlayer().getUniqueId(); + if (isBedrockPlayer(uuid) && !authmeApi.isAuthenticated(player) && authmeApi.isRegistered(name)) { + authmeApi.forceLogin(player); + messages.send(player, MessageKey.BEDROCK_AUTO_LOGGED_IN); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerAuthMeSessionRestore(RestoreSessionEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + if (isBedrockPlayer(uuid)) { + event.setCancelled(true); + } + } +} diff --git a/src/main/java/fr/xephi/authme/listener/DoubleLoginFixListener.java b/src/main/java/fr/xephi/authme/listener/DoubleLoginFixListener.java new file mode 100644 index 00000000..2c0370a4 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/DoubleLoginFixListener.java @@ -0,0 +1,35 @@ +package fr.xephi.authme.listener; +//Prevent Ghost Players + +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.service.CommonService; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.HashSet; + +public class DoubleLoginFixListener implements Listener { + @Inject + private CommonService service; + + public DoubleLoginFixListener() { + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Collection PlayerList = Bukkit.getServer().getOnlinePlayers(); + HashSet PlayerSet = new HashSet(); + for (Player ep : PlayerList) { + if (PlayerSet.contains(ep.getName().toLowerCase())) { + ep.kickPlayer(service.retrieveSingleMessage(ep.getPlayer(), MessageKey.DOUBLE_LOGIN_FIX)); + break; + } + PlayerSet.add(ep.getName().toLowerCase()); + } + } +} diff --git a/src/main/java/fr/xephi/authme/listener/GuiCaptchaHandler.java b/src/main/java/fr/xephi/authme/listener/GuiCaptchaHandler.java new file mode 100644 index 00000000..497de210 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/GuiCaptchaHandler.java @@ -0,0 +1,328 @@ +package fr.xephi.authme.listener; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.api.v3.AuthMeApi; +import fr.xephi.authme.events.LoginEvent; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import javax.inject.Inject; +import java.io.File; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; + +import static fr.xephi.authme.util.PlayerUtils.getPlayerIp; +import static fr.xephi.authme.util.PlayerUtils.isNpc; +import static org.bukkit.Bukkit.getLogger; +import static org.bukkit.Bukkit.getServer; + +/** + * This class handles ALL the GUI captcha features in the plugin. + */ +public class GuiCaptchaHandler implements Listener { + //define AuthMeApi + private final AuthMeApi authmeApi = AuthMeApi.getInstance(); + @Inject + private BukkitService bukkitService; + @Inject + private AuthMe plugin; + @Inject + private Messages messages; + @Inject + private CommonService service; + + @Inject + private Settings settings; + + + private PacketAdapter chatPacketListener; + private PacketAdapter windowPacketListener; + + //define timesLeft + private int timesLeft = 3; + //Use ConcurrentHashMap to store player and their close reason + /* We used many async tasks so there is concurrent**/ + public static ConcurrentHashMap closeReasonMap = new ConcurrentHashMap<>(); + //define randomStringSet + String randomSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz!@#%&*()_+"; + String randomString = ""; + Random randomItemSet = new Random(); + Random howManyRandom = new Random(); + private Material captchaMaterial = getRandomMaterial(); + + + private boolean isPacketListenersActive = false; + + public GuiCaptchaHandler() { + } + + private StringBuilder sb; + private final List whiteList = AuthMe.settings.getProperty(SecuritySettings.GUI_CAPTCHA_COUNTRY_WHITELIST); + + private boolean isBedrockPlayer(UUID uuid) { + if (getServer().getPluginManager().getPlugin("floodgate") != null) { + return settings.getProperty(HooksSettings.HOOK_FLOODGATE_PLAYER) && settings.getProperty(SecuritySettings.GUI_CAPTCHA_BE_COMPATIBILITY) && org.geysermc.floodgate.api.FloodgateApi.getInstance().isFloodgateId(uuid); + } + return false; + } + + + private void initializePacketListeners() { + if (!isPacketListenersActive) { + ProtocolLibrary.getProtocolManager().addPacketListener(windowPacketListener); + ProtocolLibrary.getProtocolManager().addPacketListener(chatPacketListener); + isPacketListenersActive = true; + } + } + + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.getWhoClicked() instanceof Player) { + Player player = (Player) event.getWhoClicked(); + ItemStack currentItem = event.getCurrentItem(); + if (!authmeApi.isRegistered(player.getName())) { + if (isBedrockPlayer(player.getUniqueId())) { + return; + } + if (currentItem != null && currentItem.getType().equals(captchaMaterial)) { + event.setCancelled(true); + if (!closeReasonMap.containsKey(player)) { + closeReasonMap.put(player, "verified"); + } + player.closeInventory(); + messages.send(player, MessageKey.GUI_CAPTCHA_VERIFIED); + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerLogin(PlayerLoginEvent event) { + Player player = event.getPlayer(); + bukkitService.runTaskAsynchronously(() -> { + sb = new StringBuilder(); + int howLongIsRandomString = (howManyRandom.nextInt(3) + 1); + for (int i = 0; i < howLongIsRandomString; i++) { + //生成随机索引号 + int index = randomItemSet.nextInt(randomSet.length()); + + // 从字符串中获取由索引 index 指定的字符 + char randomChar = randomSet.charAt(index); + + // 将字符追加到字符串生成器 + sb.append(randomChar); + } + if (!whiteList.isEmpty()) { + String ip = getPlayerIp(player); + if (whiteList.contains(authmeApi.getCountryCode(ip)) && ip != null) { + if (!closeReasonMap.containsKey(player)) { + closeReasonMap.put(player, "verified:whitelist"); + } + } + } + }); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onPlayerJoin(PlayerJoinEvent event) { + randomString = ""; + Player playerunreg = event.getPlayer(); + String name = playerunreg.getName(); + if (!authmeApi.isRegistered(name) && !isNpc(playerunreg) && !closeReasonMap.containsKey(playerunreg)) { + if (isBedrockPlayer(playerunreg.getUniqueId())) { + if (!closeReasonMap.containsKey(playerunreg)) { + closeReasonMap.put(playerunreg, "verified:bedrock"); + } + messages.send(playerunreg, MessageKey.GUI_CAPTCHA_VERIFIED_AUTO_BEDROCK); + return; + } + bukkitService.runTask(() -> { + randomString = sb.toString(); + Random random_blockpos = new Random(); + AtomicInteger random_num = new AtomicInteger(random_blockpos.nextInt(26)); + Inventory menu = Bukkit.createInventory(playerunreg, 27, messages.retrieveSingle(playerunreg, MessageKey.GUI_CAPTCHA_WINDOW_NAME, randomString)); + ItemStack item = new ItemStack(captchaMaterial); + ItemMeta meta = item.getItemMeta(); + try { + if (meta != null) { + meta.setDisplayName(messages.retrieveSingle(playerunreg, MessageKey.GUI_CAPTCHA_CLICKABLE_NAME, randomString)); + item.setItemMeta(meta); + } + } catch (NullPointerException e) { + getLogger().log(Level.WARNING, "Unexpected error occurred while setting item meta."); + } + windowPacketListener = new PacketAdapter(this.plugin, ListenerPriority.HIGHEST, PacketType.Play.Client.CLOSE_WINDOW) { + @Override + public void onPacketReceiving(PacketEvent event) { + Player packetPlayer = event.getPlayer(); + if (!closeReasonMap.containsKey(packetPlayer) && !authmeApi.isRegistered(packetPlayer.getName())) { + if (timesLeft <= 0) { + bukkitService.runTask(() -> { + packetPlayer.kickPlayer(service.retrieveSingleMessage(packetPlayer, MessageKey.GUI_CAPTCHA_KICK_FAILED)); + }); + timesLeft = 3; + } else { + --timesLeft; + if (timesLeft <= 0) { + bukkitService.runTask(() -> { + packetPlayer.kickPlayer(service.retrieveSingleMessage(packetPlayer, MessageKey.GUI_CAPTCHA_KICK_FAILED)); + }); + timesLeft = 3; + return; + } + messages.send(packetPlayer, MessageKey.GUI_CAPTCHA_RETRY_MESSAGE, String.valueOf(timesLeft)); + event.setCancelled(true); + random_num.set(random_blockpos.nextInt(26)); + bukkitService.runTask(() -> { + menu.clear(); + menu.setItem(random_num.get(), item); + packetPlayer.openInventory(menu); + }); + } + } + } + }; + chatPacketListener = new PacketAdapter(this.plugin, ListenerPriority.HIGHEST, PacketType.Play.Client.CHAT) { + @Override + public void onPacketReceiving(PacketEvent event) { + Player packetPlayer = event.getPlayer(); + if (!closeReasonMap.containsKey(packetPlayer) && !authmeApi.isRegistered(packetPlayer.getName())) { + messages.send(packetPlayer, MessageKey.GUI_CAPTCHA_DENIED_MESSAGE); + event.setCancelled(true); + } + } + }; + initializePacketListeners(); + //Open captcha inventory + menu.setItem(random_num.get(), item); + playerunreg.openInventory(menu); + if (settings.getProperty(SecuritySettings.GUI_CAPTCHA_TIMEOUT) > 0) { + long timeOut = settings.getProperty(SecuritySettings.GUI_CAPTCHA_TIMEOUT); + if (settings.getProperty(SecuritySettings.GUI_CAPTCHA_TIMEOUT) > settings.getProperty(RestrictionSettings.TIMEOUT)) { + bukkitService.runTask(() -> { + getLogger().warning("AuthMe detected that your GUI captcha timeout seconds(" + settings.getProperty(SecuritySettings.GUI_CAPTCHA_TIMEOUT) + ") is bigger than the Login timeout seconds(" + + settings.getProperty(RestrictionSettings.TIMEOUT) + "). To prevent issues, we will let the GUI captcha follow the Login timeout seconds, please check and modify your config."); + }); + timeOut = settings.getProperty(RestrictionSettings.TIMEOUT); + } + long finalTimeOut = timeOut; + bukkitService.runTaskLater(() -> { + if (!closeReasonMap.containsKey(playerunreg) && !authmeApi.isRegistered(playerunreg.getName())) { + playerunreg.kickPlayer(service.retrieveSingleMessage(playerunreg, MessageKey.GUI_CAPTCHA_KICK_TIMEOUT)); + timesLeft = 3; // Reset the attempt counter + } + }, finalTimeOut * 20L); + } + }); + } + } + + //This prevents players from unregistering by Admins + @EventHandler + public void onPlayerAuthMeLogin(LoginEvent event) { + Player player = event.getPlayer(); + if (!closeReasonMap.containsKey(player)) { + closeReasonMap.put(player, "verified:login"); + } + } + + + private void deletePlayerData(UUID playerUUID) { + // 获取服务器的存储文件夹路径 + File serverFolder = Bukkit.getServer().getWorldContainer(); + String worldFolderName = settings.getProperty(SecuritySettings.DELETE_PLAYER_DATA_WORLD); + // 构建playerdata文件夹路径 + File playerDataFolder = new File(serverFolder, File.separator + worldFolderName + File.separator + "playerdata"); + + // 构建玩家数据文件路径 + File playerDataFile = new File(playerDataFolder, File.separator + playerUUID + ".dat"); + + // 删除玩家数据文件 + if (playerDataFile.exists()) { + playerDataFile.delete(); + } + } + + private void deletePlayerStats(UUID playerUUID) { + // 获取服务器的存储文件夹路径 + File serverFolder = Bukkit.getServer().getWorldContainer(); + String worldFolderName = settings.getProperty(SecuritySettings.DELETE_PLAYER_DATA_WORLD); + // 构建stats文件夹路径 + File statsFolder = new File(serverFolder, File.separator + worldFolderName + File.separator + "stats"); + // 构建玩家统计数据文件路径 + File statsFile = new File(statsFolder, File.separator + playerUUID + ".json"); + // 删除玩家统计数据文件 + if (statsFile.exists()) { + statsFile.delete(); + } + } + + private void deleteAuthMePlayerData(UUID playerUUID) { + File pluginFolder = plugin.getDataFolder(); + File path = new File(pluginFolder, File.separator + "playerdata" + File.separator + playerUUID); + File dataFile = new File(path, File.separator + "data.json"); + if (dataFile.exists()) { + dataFile.delete(); + path.delete(); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + String name = player.getName(); + UUID playerUUID = event.getPlayer().getUniqueId(); + if (!authmeApi.isRegistered(name)) { + if (settings.getProperty(SecuritySettings.DELETE_UNVERIFIED_PLAYER_DATA) && !closeReasonMap.containsKey(player)) { + bukkitService.runTaskLater(() -> { + if (!player.isOnline()) { + deletePlayerData(playerUUID); + deletePlayerStats(playerUUID); + deleteAuthMePlayerData(playerUUID); + } + }, 100L); + return; + } + closeReasonMap.remove(player); + } + } + + private Material getRandomMaterial() { + Material[] allMaterials = Material.values(); + Random random = new Random(); + return allMaterials[random.nextInt(allMaterials.length)]; + } +} + + + + diff --git a/src/main/java/fr/xephi/authme/listener/ListenerService.java b/src/main/java/fr/xephi/authme/listener/ListenerService.java index d283f3e4..073b3975 100644 --- a/src/main/java/fr/xephi/authme/listener/ListenerService.java +++ b/src/main/java/fr/xephi/authme/listener/ListenerService.java @@ -1,11 +1,14 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.api.v3.AuthMeApi; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -18,13 +21,13 @@ import javax.inject.Inject; * Service class for the AuthMe listeners to determine whether an event should be canceled. */ class ListenerService implements SettingsDependent { - + private final AuthMeApi authmeApi = AuthMeApi.getInstance(); private final DataSource dataSource; private final PlayerCache playerCache; private final ValidationService validationService; - private boolean isRegistrationForced; + @Inject ListenerService(Settings settings, DataSource dataSource, PlayerCache playerCache, ValidationService validationService) { @@ -76,9 +79,18 @@ class ListenerService implements SettingsDependent { * @param player the player to verify * @return true if the associated event should be canceled, false otherwise */ + public boolean shouldCancelEvent(Player player) { + return player != null && !checkAuth(player.getName()) && !PlayerUtils.isNpc(player); } + public boolean shouldCancelInvEvent(Player player) { + try { + return !AuthMe.settings.getProperty(SecuritySettings.GUI_CAPTCHA) || authmeApi.isRegistered(player.getName()) || GuiCaptchaHandler.closeReasonMap.containsKey(player)/* || !player.getOpenInventory().getTitle().equals("请验证你是真人")*/; + } catch (Exception e) { + return true; + } + } @Override public void reload(Settings settings) { @@ -93,7 +105,7 @@ class ListenerService implements SettingsDependent { * @return true if the player may play, false otherwise */ private boolean checkAuth(String name) { - if (validationService.isUnrestricted(name) || playerCache.isAuthenticated(name)) { + if (validationService.isUnrestricted(name) || playerCache.isAuthenticated(name)){ return true; } if (!isRegistrationForced && !dataSource.isAuthAvailable(name)) { diff --git a/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java b/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java new file mode 100644 index 00000000..344290ac --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/LoginLocationFixListener.java @@ -0,0 +1,130 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.api.v3.AuthMeApi; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.TeleportUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import javax.inject.Inject; +import java.lang.reflect.Method; + + +public class LoginLocationFixListener implements Listener { + @Inject + private AuthMe plugin; + @Inject + private Messages messages; + @Inject + private Settings settings; + private final AuthMeApi authmeApi = AuthMeApi.getInstance(); + + public LoginLocationFixListener() { + } + + private static Material materialPortal = Material.matchMaterial("PORTAL"); + + private static boolean isAvailable; // false: unchecked/method not available true: method is available + BlockFace[] faces = {BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.SOUTH_EAST, BlockFace.SOUTH_WEST, BlockFace.NORTH_EAST, BlockFace.NORTH_WEST}; + + static { + if (materialPortal == null) { + materialPortal = Material.matchMaterial("PORTAL_BLOCK"); + if (materialPortal == null) { + materialPortal = Material.matchMaterial("NETHER_PORTAL"); + } + } + try { + Method getMinHeightMethod = World.class.getMethod("getMinHeight"); + isAvailable = true; + } catch (NoSuchMethodException e) { + isAvailable = false; + } + } + + private int getMinHeight(World world) { + //This keeps compatibility of 1.16.x and lower + if (isAvailable) { + return world.getMinHeight(); + } else { + return 0; + } + } + + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Location JoinLocation = player.getLocation(); + if (settings.getProperty(SecuritySettings.LOGIN_LOC_FIX_SUB_PORTAL)) { + if (!JoinLocation.getBlock().getType().equals(materialPortal) && !JoinLocation.getBlock().getRelative(BlockFace.UP).getType().equals(materialPortal)) { + return; + } + Block JoinBlock = JoinLocation.getBlock(); + boolean solved = false; + for (BlockFace face : faces) { + if (JoinBlock.getRelative(face).getType().equals(Material.AIR) && JoinBlock.getRelative(face).getRelative(BlockFace.UP).getType().equals(Material.AIR)) { + if (settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + TeleportUtils.teleport(player, JoinBlock.getRelative(face).getLocation().add(0.5, 0.1, 0.5)); + } else { + player.teleport(JoinBlock.getRelative(face).getLocation().add(0.5, 0.1, 0.5)); + } + solved = true; + break; + } + } + if (!solved) { + JoinBlock.getRelative(BlockFace.UP).breakNaturally(); + JoinBlock.breakNaturally(); + } + messages.send(player, MessageKey.LOCATION_FIX_PORTAL); + } + if (settings.getProperty(SecuritySettings.LOGIN_LOC_FIX_SUB_UNDERGROUND)) { + Material UpType = JoinLocation.getBlock().getRelative(BlockFace.UP).getType(); + World world = player.getWorld(); + int MaxHeight = world.getMaxHeight(); + int MinHeight = getMinHeight(world); + if (!UpType.isOccluding() && !UpType.equals(Material.LAVA)) { + return; + } + for (int i = MinHeight; i <= MaxHeight; i++) { + JoinLocation.setY(i); + Block JoinBlock = JoinLocation.getBlock(); + if ((JoinBlock.getRelative(BlockFace.DOWN).getType().isBlock()) + && JoinBlock.getType().equals(Material.AIR) + && JoinBlock.getRelative(BlockFace.UP).getType().equals(Material.AIR)) { + if (JoinBlock.getRelative(BlockFace.DOWN).getType().equals(Material.LAVA)) { + JoinBlock.getRelative(BlockFace.DOWN).setType(Material.DIRT); + } + if (settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + TeleportUtils.teleport(player, JoinBlock.getLocation().add(0.5, 0.1, 0.5)); + } else { + player.teleport(JoinBlock.getLocation().add(0.5, 0.1, 0.5)); + } + messages.send(player, MessageKey.LOCATION_FIX_UNDERGROUND); + break; + } + if (i == MaxHeight) { + if (settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + TeleportUtils.teleport(player, JoinBlock.getLocation().add(0.5, 1.1, 0.5)); + } else { + player.teleport(JoinBlock.getLocation().add(0.5, 1.1, 0.5)); + } + messages.send(player, MessageKey.LOCATION_FIX_UNDERGROUND_CANT_FIX); + } + } + } + + } +} diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index b125bf0a..08224841 100644 --- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -4,9 +4,9 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.service.AntiBotService; diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java index 53ea908f..fc12bab9 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -1,5 +1,6 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.api.v3.AuthMeApi; import fr.xephi.authme.data.QuickCommandsProtectionManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; @@ -18,6 +19,8 @@ import fr.xephi.authme.settings.SpawnLoader; 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.util.TeleportUtils; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.entity.HumanEntity; @@ -45,7 +48,6 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerShearEntityEvent; @@ -57,11 +59,16 @@ import java.util.Set; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT; +import static org.bukkit.Bukkit.getServer; /** * Listener class for player events. */ -public class PlayerListener implements Listener { +public class PlayerListener implements Listener{ + private final AuthMeApi authmeApi = AuthMeApi.getInstance(); + + + @Inject private Settings settings; @@ -92,9 +99,11 @@ public class PlayerListener implements Listener { @Inject private QuickCommandsProtectionManager quickCommandsProtectionManager; + // Lowest priority to apply fast protection checks @EventHandler(priority = EventPriority.LOWEST) public void onAsyncPlayerPreLoginEventLowest(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { return; } @@ -110,13 +119,18 @@ public class PlayerListener implements Listener { if (validationService.isUnrestricted(name)) { return; } - + if (settings.getProperty(HooksSettings.HOOK_FLOODGATE_PLAYER)) { + if (getServer().getPluginManager().getPlugin("floodgate") != null) { + if (org.geysermc.floodgate.api.FloodgateApi.getInstance().isFloodgateId(event.getUniqueId())) return; + } + } // Non-blocking checks try { onJoinVerifier.checkIsValidName(name); } catch (FailedVerificationException e) { event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs())); event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + } } @@ -179,7 +193,7 @@ public class PlayerListener implements Listener { @EventHandler(priority = EventPriority.NORMAL) public void onPlayerJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); - + final AuthMeApi authmeApi = AuthMeApi.getInstance(); if (!PlayerListener19Spigot.isPlayerSpawnLocationEventCalled()) { teleportationService.teleportOnJoin(player); } @@ -189,12 +203,15 @@ public class PlayerListener implements Listener { management.performJoin(player); teleportationService.teleportNewPlayerToFirstSpawn(player); + } + @EventHandler(priority = EventPriority.HIGH) // HIGH as EssentialsX listens at HIGHEST public void onJoinMessage(PlayerJoinEvent event) { final Player player = event.getPlayer(); + // Note: join message can be null, despite api documentation says not if (settings.getProperty(RegistrationSettings.REMOVE_JOIN_MESSAGE)) { event.setJoinMessage(null); @@ -329,12 +346,12 @@ public class PlayerListener implements Listener { } /* - * Limit player X and Z movements to 1 block + * Limit player X and Z movements * Deny player Y+ movements (allows falling) */ - if (from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ() + if (from.getX() == to.getX() + && from.getZ() == to.getZ() && from.getY() - to.getY() >= 0) { return; } @@ -357,9 +374,17 @@ public class PlayerListener implements Listener { Location spawn = spawnLoader.getSpawnLocation(player); if (spawn != null && spawn.getWorld() != null) { if (!player.getWorld().equals(spawn.getWorld())) { - player.teleport(spawn); + if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + TeleportUtils.teleport(player,spawn); + } else { + player.teleport(spawn); + } } else if (spawn.distance(player.getLocation()) > settings.getProperty(ALLOWED_MOVEMENT_RADIUS)) { - player.teleport(spawn); + if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) { + TeleportUtils.teleport(player,spawn); + } else { + player.teleport(spawn); + } } } } @@ -450,12 +475,12 @@ public class PlayerListener implements Listener { * Inventory interactions */ - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerPickupItem(PlayerPickupItemEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } +// @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) +// public void onPlayerPickupItem(EntityPickupItemEvent event) { +// if (listenerService.shouldCancelEvent(event)) { +// event.setCancelled(true); +// } +// } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerDropItem(PlayerDropItemEvent event) { @@ -483,15 +508,16 @@ public class PlayerListener implements Listener { return false; } Set whitelist = settings.getProperty(RestrictionSettings.UNRESTRICTED_INVENTORIES); + //append a string for String whitelist return whitelist.contains(ChatColor.stripColor(inventory.getTitle()).toLowerCase(Locale.ROOT)); } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInventoryOpen(InventoryOpenEvent event) { final HumanEntity player = event.getPlayer(); - + Player ply = (Player) event.getPlayer(); if (listenerService.shouldCancelEvent(player) - && !isInventoryWhitelisted(event.getView())) { + && !isInventoryWhitelisted(event.getView()) && listenerService.shouldCancelInvEvent(ply)) { event.setCancelled(true); /* @@ -509,4 +535,12 @@ public class PlayerListener implements Listener { event.setCancelled(true); } } +// @EventHandler(priority = EventPriority.LOWEST) +// public void onSwitchHand(PlayerSwapHandItemsEvent event) { +// Player player = event.getPlayer(); +// if (!player.isSneaking() || !player.hasPermission("keybindings.use")) +// return; +// event.setCancelled(true); +// Bukkit.dispatchCommand(event.getPlayer(), "help"); +// } } diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListenerHigherThan18.java b/src/main/java/fr/xephi/authme/listener/PlayerListenerHigherThan18.java new file mode 100644 index 00000000..66c408a1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/PlayerListenerHigherThan18.java @@ -0,0 +1,37 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.PluginSettings; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; + +import javax.inject.Inject; + +public class PlayerListenerHigherThan18 implements Listener { + @Inject + private ListenerService listenerService; + + @Inject + private Settings settings; + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerPickupItem(EntityPickupItemEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onSwitchHand(PlayerSwapHandItemsEvent event) { + Player player = event.getPlayer(); + if (player.isSneaking() && player.hasPermission("keybindings.use") && settings.getProperty(PluginSettings.MENU_UNREGISTER_COMPATIBILITY)) { + event.setCancelled(true); + Bukkit.dispatchCommand(event.getPlayer(), "help"); + } + } +} diff --git a/src/main/java/fr/xephi/authme/listener/ServerListener.java b/src/main/java/fr/xephi/authme/listener/ServerListener.java index e7e3c4a9..97cefa04 100644 --- a/src/main/java/fr/xephi/authme/listener/ServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/ServerListener.java @@ -1,8 +1,8 @@ package fr.xephi.authme.listener; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.listener.protocollib.ProtocolLibService; +import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.SpawnLoader; diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java index bceeaca7..c83b992b 100644 --- a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java @@ -34,7 +34,6 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.List; @@ -114,10 +113,6 @@ class InventoryPacketAdapter extends PacketAdapter { itemListModifier.write(0, Arrays.asList(blankInventory)); } - try { - protocolManager.sendServerPacket(player, inventoryPacket, false); - } catch (InvocationTargetException invocationExc) { - logger.logException("Error during sending blank inventory", invocationExc); - } + protocolManager.sendServerPacket(player, inventoryPacket, false); } } diff --git a/src/main/java/fr/xephi/authme/mail/EmailService.java b/src/main/java/fr/xephi/authme/mail/EmailService.java index 6aef0433..0863f594 100644 --- a/src/main/java/fr/xephi/authme/mail/EmailService.java +++ b/src/main/java/fr/xephi/authme/mail/EmailService.java @@ -40,21 +40,7 @@ public class EmailService { return sendMailSsl.hasAllInformation(); } - - /** - * Sends an email to the user with his new password. - * - * @param name the name of the player - * @param mailAddress the player's email - * @param newPass the new password - * @return true if email could be sent, false otherwise - */ - public boolean sendPasswordMail(String name, String mailAddress, String newPass) { - if (!hasAllInformation()) { - logger.warning("Cannot perform email registration: not all email settings are complete"); - return false; - } - + public boolean sendNewPasswordMail(String name, String mailAddress, String newPass,String ip,String time) { HtmlEmail email; try { email = sendMailSsl.initializeMail(mailAddress); @@ -63,8 +49,7 @@ public class EmailService { return false; } - String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass); - // Generate an image? + String mailText = replaceTagsForPasswordMail(settings.getNewPasswordEmailMessage(), name, newPass,ip,time); File file = null; if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { try { @@ -82,16 +67,16 @@ public class EmailService { } /** - * Sends an email to the user with the temporary verification code. + * Sends an email to the user with his new password. * * @param name the name of the player * @param mailAddress the player's email - * @param code the verification code + * @param newPass the new password * @return true if email could be sent, false otherwise */ - public boolean sendVerificationMail(String name, String mailAddress, String code) { + public boolean sendPasswordMail(String name, String mailAddress, String newPass, String time) { if (!hasAllInformation()) { - logger.warning("Cannot send verification email: not all email settings are complete"); + logger.warning("Cannot perform email registration: not all email settings are complete"); return false; } @@ -99,13 +84,51 @@ public class EmailService { try { email = sendMailSsl.initializeMail(mailAddress); } catch (EmailException e) { - logger.logException("Failed to create verification email with the given settings:", e); + logger.logException("Failed to create email with the given settings:", e); return false; } + String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass,time); + // Generate an image? + File file = null; + if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { + try { + file = generatePasswordImage(name, newPass); + mailText = embedImageIntoEmailContent(file, email, mailText); + } catch (IOException | EmailException e) { + logger.logException( + "Unable to send new password as image for email " + mailAddress + ":", e); + } + } + + boolean couldSendEmail = sendMailSsl.sendEmail(mailText, email); + FileUtils.delete(file); + return couldSendEmail; + } + /** + * Sends an email to the user with the temporary verification code. + * + * @param name the name of the player + * @param mailAddress the player's email + * @param code the verification code + */ + public void sendVerificationMail(String name, String mailAddress, String code, String time) { + if (!hasAllInformation()) { + logger.warning("Cannot send verification email: not all email settings are complete"); + return; + } + + HtmlEmail email; + try { + email = sendMailSsl.initializeMail(mailAddress); + } catch (EmailException e) { + logger.logException("Failed to create verification email with the given settings:", e); + return; + } + String mailText = replaceTagsForVerificationEmail(settings.getVerificationEmailMessage(), name, code, - settings.getProperty(SecuritySettings.VERIFICATION_CODE_EXPIRATION_MINUTES)); - return sendMailSsl.sendEmail(mailText, email); + settings.getProperty(SecuritySettings.VERIFICATION_CODE_EXPIRATION_MINUTES),time); + sendMailSsl.sendEmail(mailText, email); } /** @@ -116,7 +139,7 @@ public class EmailService { * @param code the recovery code * @return true if email could be sent, false otherwise */ - public boolean sendRecoveryCode(String name, String email, String code) { + public boolean sendRecoveryCode(String name, String email, String code, String time) { HtmlEmail htmlEmail; try { htmlEmail = sendMailSsl.initializeMail(email); @@ -126,10 +149,23 @@ public class EmailService { } String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(), - name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)); + name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID),time); return sendMailSsl.sendEmail(message, htmlEmail); } + public void sendShutDown(String email, String time) { + HtmlEmail htmlEmail; + try { + htmlEmail = sendMailSsl.initializeMail(email); + } catch (EmailException e) { + logger.logException("Failed to create email for recovery code:", e); + return; + } + + String message = replaceTagsForShutDownMail(settings.getShutdownEmailMessage(), time); + sendMailSsl.sendEmail(message, htmlEmail); + } + private File generatePasswordImage(String name, String newPass) throws IOException { ImageGenerator gen = new ImageGenerator(newPass); File file = new File(dataFolder, name + "_new_pass.jpg"); @@ -144,26 +180,44 @@ public class EmailService { return content.replace("", ""); } - private String replaceTagsForPasswordMail(String mailText, String name, String newPass) { + private String replaceTagsForPasswordMail(String mailText, String name, String newPass,String ip,String time) { return mailText .replace("", name) .replace("", settings.getProperty(PluginSettings.SERVER_NAME)) - .replace("", newPass); + .replace("", newPass) + .replace("", ip) + .replace("