mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-25 08:08:16 +01:00
Compare commits
6 Commits
protocolli
...
tcpshield-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bcd5b1848 | ||
|
|
1e97314495 | ||
|
|
1dc0d92dbb | ||
|
|
2dbf483c91 | ||
|
|
8f91f36148 | ||
|
|
7652943f2a |
19
.github/dependabot.yml
vendored
19
.github/dependabot.yml
vendored
@@ -14,3 +14,22 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
ignore:
|
||||
- dependency-name: com.google.code.gson:gson
|
||||
versions:
|
||||
- "> 2.2.4"
|
||||
- dependency-name: com.google.guava
|
||||
- dependency-name: me.clip:placeholderapi
|
||||
versions:
|
||||
- "> 2.10.8, < 2.11"
|
||||
- dependency-name: net.md-5:bungeecord-config
|
||||
versions:
|
||||
- "> 1.12-SNAPSHOT"
|
||||
- dependency-name: de.xxschrandxx.bca:BungeeCordAuthenticator
|
||||
versions:
|
||||
- 0.0.3
|
||||
- dependency-name: com.zaxxer:HikariCP
|
||||
versions:
|
||||
- 4.0.0
|
||||
- 4.0.2
|
||||
- 4.0.3
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -2,7 +2,7 @@
|
||||
[//]: # (If your work is in progress, please consider making a draft pull request.)
|
||||
|
||||
### Summary of your change
|
||||
[//]: # (Example: motivation, enhancement)
|
||||
[//]: # (Example: motiviation, enhancement)
|
||||
|
||||
### Related issue
|
||||
[//]: # (Reference it using '#NUMBER'. Ex: Fixes/Related #...)
|
||||
|
||||
5
.github/workflows/codeql-analysis.yml
vendored
5
.github/workflows/codeql-analysis.yml
vendored
@@ -40,7 +40,8 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 18
|
||||
# Use Java 16+, because it's minimum required version by Geyser
|
||||
java-version: 17
|
||||
cache: 'maven'
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
@@ -50,7 +51,7 @@ jobs:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Cache build process too like in the maven config
|
||||
- uses: actions/cache@v3.0.4
|
||||
- uses: actions/cache@v3.0.1
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
|
||||
5
.github/workflows/maven.yml
vendored
5
.github/workflows/maven.yml
vendored
@@ -31,7 +31,8 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 18
|
||||
# Use Java 16+, because it's minimum required version by Geyser
|
||||
java-version: 17
|
||||
cache: 'maven'
|
||||
|
||||
# Build and test (included in package)
|
||||
@@ -39,4 +40,4 @@ jobs:
|
||||
# Run non-interactive, package (with compile+test),
|
||||
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums to validate
|
||||
# possible errors in dependencies
|
||||
run: mvn test --batch-mode --threads 2.0C --no-snapshot-updates --strict-checksums --file pom.xml
|
||||
run: mvn test --batch-mode --no-snapshot-updates --strict-checksums --file pom.xml
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
* Automatically register accounts if they are not in the auth plugin database but in the FastLogin database
|
||||
* Update BungeeAuth dependency and use the new API. Please update your plugin if you still use the old one.
|
||||
* Remove deprecated API methods from the last version
|
||||
* Finally, update the IP column on every login
|
||||
* Finally update the IP column on every login
|
||||
* No duplicate session login
|
||||
* Fix timestamp parsing in newer versions of SQLite
|
||||
* Fix Spigot console command invocation sends result to in game players
|
||||
@@ -82,7 +82,7 @@
|
||||
* Fix player entry is not saved if namechangecheck is enabled
|
||||
* Fix skin applies for third-party plugins
|
||||
* Switch to mcapi.ca for uuid lookups
|
||||
* Fix BungeeCord not setting a premium uuid
|
||||
* Fix BungeeCord not setting an premium uuid
|
||||
* Fix setting skin on Cauldron
|
||||
* Fix saving on name change
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
### 1.2
|
||||
|
||||
* Fix race condition in BungeeCord
|
||||
* Fix deadlock in xAuth
|
||||
* Fix dead lock in xAuth
|
||||
* Added API methods for plugins to set their own password generator
|
||||
* Added API methods for plugins to set their own auth plugin hook
|
||||
=> Added support for AdvancedLogin
|
||||
@@ -182,7 +182,7 @@
|
||||
* Added a forwardSkin config option
|
||||
* Added premium UUID support
|
||||
* Updated to the newest changes of Spigot
|
||||
* Removes the need of a Bukkit auth plugin if you use a bungeecord one
|
||||
* Removes the need of an Bukkit auth plugin if you use a bungeecord one
|
||||
* Optimize performance and thread-safety
|
||||
* Fixed BungeeCord support
|
||||
* Changed config option auto-login to auto-register to clarify the usage
|
||||
|
||||
@@ -19,7 +19,7 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
|
||||
* No client modifications needed
|
||||
* Good performance by using async operations
|
||||
* Locale messages
|
||||
* Support for Bedrock players proxies through FloodGate
|
||||
* Support for Bedrock players proxied through FloodGate
|
||||
|
||||
## Issues
|
||||
|
||||
@@ -60,11 +60,11 @@ Possible values: `Premium`, `Cracked`, `Unknown`
|
||||
|
||||
## Requirements
|
||||
|
||||
* Java 17+
|
||||
* Java 8+
|
||||
* Server software in offlinemode:
|
||||
* Spigot (or a fork e.g. Paper) 1.8.8+
|
||||
* Protocol plugin:
|
||||
* [ProtocolLib 5.0+](https://www.spigotmc.org/resources/protocollib.1997/) or
|
||||
* [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
|
||||
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
|
||||
* Latest BungeeCord (or a fork e.g. Waterfall)
|
||||
* An auth plugin.
|
||||
@@ -78,6 +78,7 @@ Possible values: `Premium`, `Cracked`, `Unknown`
|
||||
* [CrazyLogin](https://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
||||
* [LoginSecurity](https://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
||||
* [LogIt](https://github.com/games647/LogIt)
|
||||
* [SodionAuth (2.0+)](https://github.com/MohistMC/SodionAuth)
|
||||
* [UltraAuth](https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
|
||||
* [UserLogin](https://www.spigotmc.org/resources/userlogin.80669/)
|
||||
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||
@@ -86,6 +87,7 @@ Possible values: `Premium`, `Cracked`, `Unknown`
|
||||
|
||||
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
|
||||
* [BungeeAuthenticator](https://www.spigotmc.org/resources/bungeecordauthenticator.87669/)
|
||||
* [SodionAuth (2.0+)](https://github.com/MohistMC/SodionAuth)
|
||||
|
||||
## Network requests
|
||||
|
||||
|
||||
@@ -29,14 +29,10 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<properties>
|
||||
<nettyVersion>4.1.77.Final</nettyVersion>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -49,6 +45,7 @@
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
@@ -121,7 +118,10 @@
|
||||
<!-- ProtocolLib -->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
|
||||
@@ -164,7 +164,7 @@
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.19-R0.1-SNAPSHOT</version>
|
||||
<version>1.18-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<!-- Use our own newer api version -->
|
||||
<exclusions>
|
||||
@@ -182,24 +182,11 @@
|
||||
<version>1.0.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>datafixerupper</artifactId>
|
||||
<version>5.0.28</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--Library for listening and sending Minecraft packets-->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<version>4.8.0</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -214,7 +201,7 @@
|
||||
<groupId>com.github.ProtocolSupport</groupId>
|
||||
<artifactId>ProtocolSupport</artifactId>
|
||||
<!--4.29.dev after commit about API improvements-->
|
||||
<version>master-66b494a8dd-1</version>
|
||||
<version>3a80c661fe</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -228,7 +215,7 @@
|
||||
<dependency>
|
||||
<groupId>org.geysermc.floodgate</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>2.2.0-SNAPSHOT</version>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -268,7 +255,7 @@
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.2</version>
|
||||
<version>2.11.1</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
@@ -283,7 +270,7 @@
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.6.0-beta2</version>
|
||||
<version>5.4.0</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
@@ -364,34 +351,5 @@
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<version>1.71</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>${nettyVersion}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
<version>${nettyVersion}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Provided by the spigot, required for testing ProtocolLib -->
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -26,14 +26,11 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a client connecting to the server.
|
||||
*
|
||||
@@ -45,33 +42,30 @@ public class BukkitLoginSession extends LoginSession {
|
||||
|
||||
private final byte[] verifyToken;
|
||||
|
||||
private final ClientPublicKey clientPublicKey;
|
||||
|
||||
private boolean verified;
|
||||
|
||||
private SkinProperty skinProperty;
|
||||
|
||||
public BukkitLoginSession(String username, byte[] verifyToken, ClientPublicKey publicKey, boolean registered,
|
||||
StoredProfile profile) {
|
||||
public BukkitLoginSession(String username, byte[] verifyToken, boolean registered
|
||||
, StoredProfile profile) {
|
||||
super(username, registered, profile);
|
||||
|
||||
this.clientPublicKey = publicKey;
|
||||
this.verifyToken = verifyToken.clone();
|
||||
}
|
||||
|
||||
//available for BungeeCord
|
||||
public BukkitLoginSession(String username, boolean registered) {
|
||||
this(username, EMPTY_ARRAY, null, registered, null);
|
||||
this(username, EMPTY_ARRAY, registered, null);
|
||||
}
|
||||
|
||||
//cracked player
|
||||
public BukkitLoginSession(String username, StoredProfile profile) {
|
||||
this(username, EMPTY_ARRAY, null, false, profile);
|
||||
this(username, EMPTY_ARRAY, false, profile);
|
||||
}
|
||||
|
||||
//ProtocolSupport
|
||||
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
|
||||
this(username, EMPTY_ARRAY, null, registered, profile);
|
||||
this(username, EMPTY_ARRAY, registered, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,11 +79,6 @@ public class BukkitLoginSession extends LoginSession {
|
||||
return verifyToken.clone();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClientPublicKey getClientPublicKey() {
|
||||
return clientPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return premium skin if available
|
||||
*/
|
||||
|
||||
@@ -33,11 +33,8 @@ import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -64,7 +61,7 @@ public class BungeeManager {
|
||||
private final FastLoginBukkit plugin;
|
||||
private boolean enabled;
|
||||
|
||||
private final Collection<UUID> firedJoinEvents = new HashSet<>();
|
||||
private final Set<UUID> firedJoinEvents = new HashSet<>();
|
||||
|
||||
public BungeeManager(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -90,66 +87,33 @@ public class BungeeManager {
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
enabled = detectProxy();
|
||||
try {
|
||||
enabled = detectProxy();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().warn("Cannot check proxy support. Fallback to non-proxy mode", ex);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
proxyIds = loadBungeeCordIds();
|
||||
registerPluginChannels();
|
||||
plugin.getLog().info("Found enabled proxy configuration");
|
||||
plugin.getLog().info("Remember to follow the proxy guide to complete your setup");
|
||||
} else {
|
||||
plugin.getLog().warn("Disabling Minecraft proxy configuration. Assuming direct connections from now on.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProxySupported(String className, String fieldName)
|
||||
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||
return Class.forName(className).getDeclaredField(fieldName).getBoolean(null);
|
||||
}
|
||||
|
||||
private boolean isVelocityEnabled()
|
||||
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException,
|
||||
NoSuchMethodException, InvocationTargetException {
|
||||
private boolean isProxySupported(String className, String fieldName) {
|
||||
try {
|
||||
Class<?> globalConfig = Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
|
||||
Object global = globalConfig.getDeclaredMethod("get").invoke(null);
|
||||
Object proxiesConfiguration = global.getClass().getDeclaredField("proxies").get(global);
|
||||
|
||||
Field velocitySectionField = proxiesConfiguration.getClass().getDeclaredField("velocity");
|
||||
Object velocityConfig = velocitySectionField.get(proxiesConfiguration);
|
||||
|
||||
return velocityConfig.getClass().getDeclaredField("enabled").getBoolean(velocityConfig);
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
// try again using the older Paper configuration, because the old class file still exists in newer versions
|
||||
if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) {
|
||||
return true;
|
||||
}
|
||||
return Class.forName(className).getDeclaredField(fieldName).getBoolean(null);
|
||||
} catch (ClassNotFoundException notFoundEx) {
|
||||
//ignore server has no proxy support
|
||||
} catch (NoSuchFieldException | IllegalAccessException noSuchFieldException) {
|
||||
plugin.getLog().warn("Cannot access proxy field", noSuchFieldException);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean detectProxy() {
|
||||
try {
|
||||
if (isProxySupported("org.spigotmc.SpigotConfig", "bungee")) {
|
||||
return true;
|
||||
}
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
// leave stacktrace for class not found out
|
||||
plugin.getLog().warn("Cannot check for BungeeCord support: {}", classNotFoundException.getMessage());
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
plugin.getLog().warn("Cannot check for BungeeCord support", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
return isVelocityEnabled();
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
plugin.getLog().warn("Cannot check for Velocity support in Paper: {}", classNotFoundException.getMessage());
|
||||
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) {
|
||||
plugin.getLog().warn("Cannot check for Velocity support in Paper", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
return isProxySupported("org.spigotmc.SpigotConfig", "bungee")
|
||||
|| isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport");
|
||||
}
|
||||
|
||||
private void registerPluginChannels() {
|
||||
@@ -183,7 +147,9 @@ public class BungeeManager {
|
||||
|
||||
Files.deleteIfExists(legacyFile);
|
||||
try (Stream<String> lines = Files.lines(proxiesFile)) {
|
||||
return lines.map(String::trim).map(UUID::fromString).collect(toSet());
|
||||
return lines.map(String::trim)
|
||||
.map(UUID::fromString)
|
||||
.collect(toSet());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLog().error("Failed to read proxies", ex);
|
||||
@@ -210,7 +176,7 @@ public class BungeeManager {
|
||||
/**
|
||||
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
|
||||
* BungeeCord messages after the PlayerJoinEvent fires including the delay.
|
||||
* <p>
|
||||
*
|
||||
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
|
||||
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
|
||||
* a certain time after the player join event fired.
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
|
||||
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
|
||||
@@ -55,6 +56,8 @@ import java.util.concurrent.ConcurrentMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
@@ -104,6 +107,15 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
bungeeManager = new BungeeManager(this);
|
||||
bungeeManager.initialize();
|
||||
|
||||
// getServer().getPluginManager().registerEvents(new Listener() {
|
||||
//
|
||||
// @EventHandler
|
||||
// void onHandshake(PlayerHandshakeEvent handshakeEvent) {
|
||||
// handshakeEvent.setCancelled(false);
|
||||
// handshakeEvent.setSocketAddressHostname("192.168.0.1");
|
||||
// }
|
||||
// }, this);
|
||||
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
if (bungeeManager.isEnabled()) {
|
||||
markInitialized();
|
||||
@@ -114,12 +126,23 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
}
|
||||
|
||||
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getAntiBot()), this);
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getRateLimiter()), this);
|
||||
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||
ProtocolLibListener.register(this, core.getAntiBot(), core.getConfig().getBoolean("verifyClientKeys"));
|
||||
ProtocolLibListener.register(this, core.getRateLimiter());
|
||||
|
||||
if (isPluginInstalled("floodgate")) {
|
||||
printFloodgateWarning();
|
||||
if (getConfig().getBoolean("floodgatePrefixWorkaround")){
|
||||
ManualNameChange.register(this, floodgateService);
|
||||
logger.info("Floodgate prefix injection workaround has been enabled.");
|
||||
logger.info("If you have problems joining the server, try disabling it in the configuration.");
|
||||
} else {
|
||||
logger.warn("We have detected that you are running FastLogin alongside Floodgate and ProtocolLib.");
|
||||
logger.warn("Currently there is an issue with FastLogin that prevents Floodgate name prefixes from showing up "
|
||||
+ "when it is together used with ProtocolLib.");
|
||||
logger.warn("If you would like to use Floodgate name prefixes, you can enable an experimental workaround by changing "
|
||||
+ "the value 'floodgatePrefixWorkaround' to true in config.yml.");
|
||||
logger.warn("For more information visit https://github.com/games647/FastLogin/issues/493");
|
||||
}
|
||||
}
|
||||
|
||||
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
|
||||
@@ -155,21 +178,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
dependencyWarnings();
|
||||
}
|
||||
|
||||
private void printFloodgateWarning() {
|
||||
if (getConfig().getBoolean("floodgatePrefixWorkaround")) {
|
||||
ManualNameChange.register(this, floodgateService);
|
||||
logger.info("Floodgate prefix injection workaround has been enabled.");
|
||||
logger.info("If you have problems joining the server, try disabling it in the configuration.");
|
||||
} else {
|
||||
logger.warn("We have detected that you are running FastLogin alongside Floodgate and ProtocolLib.");
|
||||
logger.warn("Currently there is an issue with FastLogin that prevents Floodgate name prefixes from "
|
||||
+ "showing up when it is together used with ProtocolLib.");
|
||||
logger.warn("If you would like to use Floodgate name prefixes, you can enable an experimental "
|
||||
+ "workaround by changing the value 'floodgatePrefixWorkaround' to true in config.yml.");
|
||||
logger.warn("For more information visit https://github.com/games647/FastLogin/issues/493");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initializeFloodgate() {
|
||||
if (getServer().getPluginManager().getPlugin("Geyser-Spigot") != null) {
|
||||
geyserService = new GeyserService(GeyserImpl.getInstance(), core);
|
||||
@@ -206,10 +214,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
logger.error("Failed to unregister placeholder", exception);
|
||||
}
|
||||
}
|
||||
|
||||
// if (isPluginInstalled("ProtocolLib")) {
|
||||
// ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(this);
|
||||
// }
|
||||
}
|
||||
|
||||
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
|
||||
@@ -337,6 +341,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
||||
+ "Floodgate 2.0 from https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/dev%252F2.0/");
|
||||
logger.warn("Don't forget to update Geyser to a supported version as well from "
|
||||
+ "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/floodgate-2.0/");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
@@ -43,16 +40,6 @@ public class PremiumPlaceholder extends PlaceholderExpansion {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> getPlaceholders() {
|
||||
return Collections.singletonList(PLACEHOLDER_VARIABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer player, @NotNull String identifier) {
|
||||
// player is null if offline
|
||||
|
||||
@@ -42,10 +42,9 @@ public class CrackedCommand extends ToggleCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
|
||||
String[] args) {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
onCrackedSelf(sender);
|
||||
onCrackedSelf(sender, command, args);
|
||||
} else {
|
||||
onCrackedOther(sender, command, args);
|
||||
}
|
||||
@@ -53,7 +52,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onCrackedSelf(CommandSender sender) {
|
||||
private void onCrackedSelf(CommandSender sender, Command cmd, String[] args) {
|
||||
if (isConsole(sender)) {
|
||||
return;
|
||||
}
|
||||
@@ -62,20 +61,25 @@ public class CrackedCommand extends ToggleCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: load async if
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
plugin.getCore().sendLocaleMessage("remove-premium", sender);
|
||||
|
||||
profile.setPremium(false);
|
||||
profile.setId(null);
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
if (plugin.getBungeeManager().isEnabled()) {
|
||||
sendBungeeActivateMessage(sender, sender.getName(), false);
|
||||
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("not-premium", sender);
|
||||
//todo: load async if
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
plugin.getCore().sendLocaleMessage("remove-premium", sender);
|
||||
|
||||
profile.setPremium(false);
|
||||
profile.setId(null);
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("not-premium", sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,9 @@ public class PremiumCommand extends ToggleCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
|
||||
String[] args) {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
onPremiumSelf(sender);
|
||||
onPremiumSelf(sender, command, args);
|
||||
} else {
|
||||
onPremiumOther(sender, command, args);
|
||||
}
|
||||
@@ -60,7 +59,7 @@ public class PremiumCommand extends ToggleCommand {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPremiumSelf(CommandSender sender) {
|
||||
private void onPremiumSelf(CommandSender sender, Command cmd, String[] args) {
|
||||
if (isConsole(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract class ToggleCommand implements CommandExecutor {
|
||||
plugin.getBungeeManager().sendPluginMessage((PluginMessageRecipient) invoker, message);
|
||||
} else {
|
||||
Optional<? extends Player> optPlayer = Bukkit.getServer().getOnlinePlayers().stream().findFirst();
|
||||
if (!optPlayer.isPresent()) {
|
||||
if (optPlayer.isEmpty()) {
|
||||
plugin.getLog().info("No player online to send a plugin message to the proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ package com.github.games647.fastlogin.bukkit.event;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
@@ -36,7 +35,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final LoginSession session;
|
||||
private final StoredProfile profile;
|
||||
private boolean cancelled;
|
||||
@@ -70,10 +69,10 @@ public class BukkitFastLoginAutoLoginEvent extends Event implements FastLoginAut
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,13 @@ package com.github.games647.fastlogin.bukkit.event;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final String username;
|
||||
private final LoginSource source;
|
||||
private final StoredProfile profile;
|
||||
@@ -65,10 +64,10 @@ public class BukkitFastLoginPreLoginEvent extends Event implements FastLoginPreL
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,13 @@ package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final StoredProfile profile;
|
||||
private final PremiumToggleReason reason;
|
||||
|
||||
@@ -56,10 +55,10 @@ public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLogi
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
/**
|
||||
* GitHub: <a href="https://github.com/Xephi/AuthMeReloaded/">...</a>
|
||||
* GitHub: https://github.com/Xephi/AuthMeReloaded/
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: <a href="https://dev.bukkit.org/bukkit-plugins/authme-reloaded/">...</a>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/authme-reloaded/
|
||||
* <p>
|
||||
* Spigot: <a href="https://www.spigotmc.org/resources/authme-reloaded.6269/">...</a>
|
||||
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
|
||||
*/
|
||||
public class AuthMeHook implements AuthPlugin<Player>, Listener {
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
@@ -44,11 +43,11 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: <a href="https://github.com/ST-DDT/CrazyLogin">...</a>
|
||||
* GitHub: https://github.com/ST-DDT/CrazyLogin
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: <a href="https://dev.bukkit.org/server-mods/crazylogin/">...</a>
|
||||
* Bukkit: https://dev.bukkit.org/server-mods/crazylogin/
|
||||
*/
|
||||
public class CrazyLoginHook implements AuthPlugin<Player> {
|
||||
|
||||
@@ -135,8 +134,15 @@ public class CrazyLoginHook implements AuthPlugin<Player> {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected PlayerListener getListener() {
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(crazyLoginPlugin.getClass(), PlayerListener.class, true);
|
||||
return (PlayerListener) accessor.get(crazyLoginPlugin);
|
||||
private PlayerListener getListener() {
|
||||
PlayerListener listener;
|
||||
try {
|
||||
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
|
||||
} catch (IllegalAccessException ex) {
|
||||
plugin.getLog().error("Failed to get the listener instance for auto login", ex);
|
||||
listener = null;
|
||||
}
|
||||
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.time.Instant;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: <a href="https://github.com/XziomekX/LogIt">...</a>
|
||||
* GitHub: https://github.com/XziomekX/LogIt
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
|
||||
@@ -36,13 +36,13 @@ import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: <a href="https://github.com/lenis0012/LoginSecurity-2">...</a>
|
||||
* GitHub: https://github.com/lenis0012/LoginSecurity-2
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: <a href="https://dev.bukkit.org/bukkit-plugins/loginsecurity/">...</a>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/loginsecurity/
|
||||
* <p>
|
||||
* Spigot: <a href="https://www.spigotmc.org/resources/loginsecurity.19362/">...</a>
|
||||
* Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/
|
||||
*/
|
||||
public class LoginSecurityHook implements AuthPlugin<Player> {
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ import ultraauth.managers.PlayerManager;
|
||||
/**
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: <a href="https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/">...</a>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/
|
||||
* <p>
|
||||
* Spigot: <a href="https://www.spigotmc.org/resources/ultraauth.17044/">...</a>
|
||||
* Spigot: https://www.spigotmc.org/resources/ultraauth.17044/
|
||||
*/
|
||||
public class UltraAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
|
||||
@@ -38,18 +38,18 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: <a href="https://github.com/LycanDevelopment/xAuth/">...</a>
|
||||
* GitHub: https://github.com/LycanDevelopment/xAuth/
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: <a href="https://dev.bukkit.org/bukkit-plugins/xauth/">...</a>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/xauth/
|
||||
*/
|
||||
public class XAuthHook implements AuthPlugin<Player> {
|
||||
public class xAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public XAuthHook(FastLoginBukkit plugin) {
|
||||
public xAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ public class ConnectionListener implements Listener {
|
||||
|
||||
String sessionId = plugin.getSessionId(player.spigot().getRawAddress());
|
||||
plugin.getLog().info("No on-going login session for player: {} with ID {}. ", player, sessionId);
|
||||
plugin.getLog().info("Setups using Minecraft proxies will start delayed "
|
||||
+ "when the command from the proxy is received");
|
||||
plugin.getLog().info("Setups using Minecraft proxies will start delayed " +
|
||||
"when the command from the proxy is received");
|
||||
} else {
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.destroystokyo.paper.profile.ProfileProperty;
|
||||
import com.github.games647.craftapi.model.skin.Textures;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
@@ -25,69 +25,34 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Encoder;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import lombok.val;
|
||||
|
||||
/**
|
||||
* Encryption and decryption minecraft util for connection between servers
|
||||
* and paid Minecraft account clients.
|
||||
*
|
||||
* @see net.minecraft.server.MinecraftEncryption
|
||||
*/
|
||||
final class EncryptionUtil {
|
||||
public class EncryptionUtil {
|
||||
|
||||
public static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
public static final String KEY_PAIR_ALGORITHM = "RSA";
|
||||
|
||||
private static final int RSA_LENGTH = 1_024;
|
||||
|
||||
private static final PublicKey MOJANG_SESSION_KEY;
|
||||
private static final int LINE_LENGTH = 76;
|
||||
private static final Encoder KEY_ENCODER = Base64.getMimeEncoder(
|
||||
LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
static {
|
||||
try {
|
||||
MOJANG_SESSION_KEY = loadMojangSessionKey();
|
||||
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) {
|
||||
throw new RuntimeException("Failed to load Mojang session key", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private EncryptionUtil() {
|
||||
throw new RuntimeException("No instantiation of utility classes allowed");
|
||||
// utility
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,10 +61,11 @@ final class EncryptionUtil {
|
||||
* @return The RSA key pair.
|
||||
*/
|
||||
public static KeyPair generateKeyPair() {
|
||||
// KeyPair b()
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
|
||||
|
||||
keyPairGenerator.initialize(RSA_LENGTH);
|
||||
keyPairGenerator.initialize(1_024);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
// Should be existing in every vm
|
||||
@@ -112,9 +78,10 @@ final class EncryptionUtil {
|
||||
* in a login session.
|
||||
*
|
||||
* @param random random generator
|
||||
* @return a token with 4 bytes long
|
||||
* @return an error with 4 bytes long
|
||||
*/
|
||||
public static byte[] generateVerifyToken(Random random) {
|
||||
// extracted from LoginListener
|
||||
byte[] token = new byte[VERIFY_TOKEN_LENGTH];
|
||||
random.nextBytes(token);
|
||||
return token;
|
||||
@@ -123,91 +90,68 @@ final class EncryptionUtil {
|
||||
/**
|
||||
* Generate the server id based on client and server data.
|
||||
*
|
||||
* @param serverId session for the current login attempt
|
||||
* @param sessionId session for the current login attempt
|
||||
* @param sharedSecret shared secret between the client and the server
|
||||
* @param publicKey public key of the server
|
||||
* @param publicKey public key of the server
|
||||
* @return the server id formatted as a hexadecimal string.
|
||||
*/
|
||||
public static String getServerIdHashString(String serverId, SecretKey sharedSecret, PublicKey publicKey) {
|
||||
byte[] serverHash = getServerIdHash(serverId, publicKey, sharedSecret);
|
||||
return (new BigInteger(serverHash)).toString(16);
|
||||
public static String getServerIdHashString(String sessionId, SecretKey sharedSecret, PublicKey publicKey) {
|
||||
// found in LoginListener
|
||||
try {
|
||||
byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret);
|
||||
return (new BigInteger(serverHash)).toString(16);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the content and extracts the key spec.
|
||||
*
|
||||
* @param privateKey private server key
|
||||
* @param sharedKey the encrypted shared key
|
||||
* @param sharedKey the encrypted shared key
|
||||
* @return shared secret key
|
||||
* @throws GeneralSecurityException if it fails to decrypt the data
|
||||
*/
|
||||
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey)
|
||||
throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
|
||||
BadPaddingException, InvalidKeyException {
|
||||
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws GeneralSecurityException {
|
||||
// SecretKey a(PrivateKey var0, byte[] var1)
|
||||
return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES");
|
||||
}
|
||||
|
||||
public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimestamp)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
if (clientKey.isExpired(verifyTimestamp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Signature verifier = Signature.getInstance("SHA1withRSA");
|
||||
// key of the signer
|
||||
verifier.initVerify(MOJANG_SESSION_KEY);
|
||||
verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII));
|
||||
return verifier.verify(clientKey.signature());
|
||||
}
|
||||
|
||||
public static boolean verifyNonce(byte[] expected, PrivateKey decryptionKey, byte[] encryptedNonce)
|
||||
throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
|
||||
BadPaddingException, InvalidKeyException {
|
||||
byte[] decryptedNonce = decrypt(decryptionKey, encryptedNonce);
|
||||
return Arrays.equals(expected, decryptedNonce);
|
||||
}
|
||||
|
||||
public static boolean verifySignedNonce(byte[] nonce, PublicKey clientKey, long signatureSalt, byte[] signature)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
Signature verifier = Signature.getInstance("SHA256withRSA");
|
||||
// key of the signer
|
||||
verifier.initVerify(clientKey);
|
||||
|
||||
verifier.update(nonce);
|
||||
verifier.update(Longs.toByteArray(signatureSalt));
|
||||
return verifier.verify(signature);
|
||||
}
|
||||
|
||||
private static PublicKey loadMojangSessionKey()
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
val keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
|
||||
val keyData = Resources.toByteArray(keyUrl);
|
||||
val keySpec = new X509EncodedKeySpec(keyData);
|
||||
|
||||
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
||||
}
|
||||
|
||||
private static String toSignable(ClientPublicKey clientPublicKey) {
|
||||
long expiry = clientPublicKey.expiry().toEpochMilli();
|
||||
String encoded = KEY_ENCODER.encodeToString(clientPublicKey.key().getEncoded());
|
||||
return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n";
|
||||
}
|
||||
|
||||
private static byte[] decrypt(PrivateKey key, byte[] data)
|
||||
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
|
||||
IllegalBlockSizeException, BadPaddingException {
|
||||
public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException {
|
||||
// b(Key var0, byte[] var1)
|
||||
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
return decrypt(cipher, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypted the given data using the cipher.
|
||||
*
|
||||
* @param cipher decryption cypher initialized with the private key
|
||||
* @param data the encrypted data
|
||||
* @return clear text data
|
||||
* @throws GeneralSecurityException if it fails to decrypt the data
|
||||
*/
|
||||
private static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException {
|
||||
// inlined: byte[] a(int var0, Key var1, byte[] var2), Cipher a(int var0, String var1, Key
|
||||
// var2)
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
private static byte[] getServerIdHash(String sessionId, PublicKey publicKey, SecretKey sharedSecret) {
|
||||
@SuppressWarnings("deprecation")
|
||||
Hasher hasher = Hashing.sha1().newHasher();
|
||||
private static byte[] getServerIdHash(
|
||||
String sessionId, PublicKey publicKey, SecretKey sharedSecret)
|
||||
throws NoSuchAlgorithmException {
|
||||
// byte[] a(String var0, PublicKey var1, SecretKey var2)
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
|
||||
hasher.putBytes(sessionId.getBytes(StandardCharsets.ISO_8859_1));
|
||||
hasher.putBytes(sharedSecret.getEncoded());
|
||||
hasher.putBytes(publicKey.getEncoded());
|
||||
// inlined from byte[] a(String var0, byte[]... var1)
|
||||
digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1));
|
||||
digest.update(sharedSecret.getEncoded());
|
||||
digest.update(publicKey.getEncoded());
|
||||
|
||||
return hasher.hash().asBytes();
|
||||
return digest.digest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,19 +32,19 @@ import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.START;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
|
||||
/**
|
||||
* Manually inject Floodgate player name prefixes.
|
||||
* <br>
|
||||
* This is used as a workaround, because Floodgate fails to inject
|
||||
* the prefixes when it's used together with ProtocolLib and FastLogin.
|
||||
* <br>
|
||||
* For more information visit: <a href="https://github.com/games647/FastLogin/issues/493">...</a>
|
||||
* For more information visit: https://github.com/games647/FastLogin/issues/493
|
||||
*/
|
||||
public class ManualNameChange extends PacketAdapter {
|
||||
|
||||
@@ -61,41 +61,25 @@ public class ManualNameChange extends PacketAdapter {
|
||||
|
||||
public static void register(FastLoginBukkit plugin, FloodgateService floodgate) {
|
||||
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
||||
// ProtocolLibrary.getProtocolManager()
|
||||
// .getAsynchronousManager()
|
||||
// .registerAsyncHandler(new ManualNameChange(plugin, floodgate))
|
||||
// .start();
|
||||
ProtocolLibrary.getProtocolManager()
|
||||
.getAsynchronousManager()
|
||||
.registerAsyncHandler(new ManualNameChange(plugin, floodgate))
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
String username = readUsername(packet);
|
||||
WrappedGameProfile originalProfile = packet.getGameProfiles().read(0);
|
||||
|
||||
if (floodgate.getBedrockPlayer(username) == null) {
|
||||
if (floodgate.getBedrockPlayer(originalProfile.getName()) == null) {
|
||||
//not a Floodgate player, no need to add a prefix
|
||||
return;
|
||||
}
|
||||
|
||||
packet.setMeta("original_name", username);
|
||||
String prefixedName = FloodgateApi.getInstance().getPlayerPrefix() + username;
|
||||
setUsername(packet, prefixedName);
|
||||
}
|
||||
|
||||
private void setUsername(PacketContainer packet, String name) {
|
||||
if (packet.getGameProfiles().size() > 0) {
|
||||
WrappedGameProfile updatedProfile = new WrappedGameProfile(UUID.randomUUID(), name);
|
||||
packet.getGameProfiles().write(0, updatedProfile);
|
||||
} else {
|
||||
packet.getStrings().write(0, name);
|
||||
}
|
||||
}
|
||||
|
||||
private String readUsername(PacketContainer packet) {
|
||||
if (packet.getGameProfiles().size() > 0) {
|
||||
return packet.getGameProfiles().read(0).getName();
|
||||
} else {
|
||||
return packet.getStrings().read(0);
|
||||
}
|
||||
packet.setMeta("original_name", originalProfile.getName());
|
||||
String prefixedName = FloodgateApi.getInstance().getPlayerPrefix() + originalProfile.getName();
|
||||
WrappedGameProfile updatedProfile = originalProfile.withName(prefixedName);
|
||||
packet.getGameProfiles().write(0, updatedProfile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
@@ -45,9 +45,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
|
||||
private final ClientPublicKey clientKey;
|
||||
private final PublicKey serverKey;
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private final Random random;
|
||||
|
||||
@@ -55,13 +53,12 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
||||
private final String username;
|
||||
|
||||
public NameCheckTask(FastLoginBukkit plugin, Random random, Player player, PacketEvent packetEvent,
|
||||
String username, ClientPublicKey clientKey, PublicKey serverKey) {
|
||||
String username, PublicKey publicKey) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.clientKey = clientKey;
|
||||
this.serverKey = serverKey;
|
||||
this.publicKey = publicKey;
|
||||
this.random = random;
|
||||
this.player = player;
|
||||
this.username = username;
|
||||
@@ -69,11 +66,11 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// try {
|
||||
super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey));
|
||||
// } finally {
|
||||
// ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
// }
|
||||
try {
|
||||
super.onLogin(username, new ProtocolLibLoginSource(player, random, publicKey));
|
||||
} finally {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,8 +84,8 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
||||
//Minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile,
|
||||
String username, boolean registered) {
|
||||
public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile
|
||||
, String username, boolean registered) {
|
||||
try {
|
||||
source.enableOnlinemode();
|
||||
} catch (Exception ex) {
|
||||
@@ -100,14 +97,13 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
||||
core.getPendingLogin().put(ip + username, new Object());
|
||||
|
||||
byte[] verify = source.getVerifyToken();
|
||||
ClientPublicKey clientKey = source.getClientKey();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, clientKey, registered, profile);
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, registered, profile);
|
||||
plugin.putSession(player.getAddress(), playerSession);
|
||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||
// synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
// }
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,33 +30,12 @@ import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
import lombok.var;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
|
||||
@@ -71,36 +50,30 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
private final AntiBotService antiBotService;
|
||||
private final RateLimiter rateLimiter;
|
||||
|
||||
private final boolean verifyClientKeys;
|
||||
|
||||
public ProtocolLibListener(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
|
||||
public ProtocolLibListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
//run async in order to not block the server, because we are making api calls to Mojang
|
||||
super(params()
|
||||
.plugin(plugin)
|
||||
.types(START, ENCRYPTION_BEGIN));
|
||||
.types(START, ENCRYPTION_BEGIN)
|
||||
.optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.antiBotService = antiBotService;
|
||||
this.verifyClientKeys = verifyClientKeys;
|
||||
this.rateLimiter = rateLimiter;
|
||||
}
|
||||
|
||||
public static void register(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
|
||||
public static void register(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
||||
// TODO: make synchronous processing, but do web or database requests async
|
||||
ProtocolLibrary.getProtocolManager()
|
||||
.addPacketListener(new ProtocolLibListener(plugin, antiBotService, verifyClientKeys));
|
||||
.getAsynchronousManager()
|
||||
.registerAsyncHandler(new ProtocolLibListener(plugin, rateLimiter))
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
plugin.getLog().info("New packet {} from {}; Cancellation: {}, Auth-Plugin: {}, Initialized: {}, Meta: {}",
|
||||
packetEvent.getPacketType(), packetEvent.getPlayer(), packetEvent.isCancelled(),
|
||||
plugin.getCore().getAuthPluginHook(), !plugin.isServerFullyStarted(),
|
||||
packetEvent.getPacket().getMeta(SOURCE_META_KEY)
|
||||
);
|
||||
|
||||
if (packetEvent.isCancelled()
|
||||
|| plugin.getCore().getAuthPluginHook() == null
|
||||
|| !plugin.isServerFullyStarted()) {
|
||||
@@ -115,32 +88,18 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
Player sender = packetEvent.getPlayer();
|
||||
PacketType packetType = packetEvent.getPacketType();
|
||||
if (packetType == START) {
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
InetSocketAddress address = sender.getAddress();
|
||||
String username = getUsername(packet);
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
sender.kickPlayer(message);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
//player.getName() won't work at this state
|
||||
onLoginStart(packetEvent, sender, username);
|
||||
break;
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", sender);
|
||||
return;
|
||||
}
|
||||
|
||||
onLogin(packetEvent, sender);
|
||||
} else {
|
||||
onEncryptionBegin(packetEvent, sender);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFastLoginPacket(PacketEvent packetEvent) {
|
||||
private Boolean isFastLoginPacket(PacketEvent packetEvent) {
|
||||
return packetEvent.getPacket().getMeta(SOURCE_META_KEY)
|
||||
.map(val -> val.equals(plugin.getName()))
|
||||
.orElse(false);
|
||||
@@ -149,122 +108,32 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
|
||||
BukkitLoginSession session = plugin.getSession(sender.getAddress());
|
||||
if (session == null) {
|
||||
plugin.getLog().warn("Profile {} tried to send encryption response at invalid state", sender.getAddress());
|
||||
sender.kickPlayer(plugin.getCore().getMessage("invalid-request"));
|
||||
} else {
|
||||
byte[] expectedVerifyToken = session.getVerifyToken();
|
||||
if (verifyNonce(sender, packetEvent.getPacket(), session.getClientPublicKey(), expectedVerifyToken)) {
|
||||
// packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
|
||||
Runnable verifyTask = new VerifyResponseTask(
|
||||
plugin, packetEvent, sender, session, sharedSecret, keyPair
|
||||
);
|
||||
verifyTask.run();
|
||||
// plugin.getScheduler().runAsync(verifyTask);
|
||||
} else {
|
||||
sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token"));
|
||||
}
|
||||
}
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
|
||||
plugin.getScheduler().runAsync(verifyTask);
|
||||
}
|
||||
|
||||
private boolean verifyNonce(Player sender, PacketContainer packet,
|
||||
ClientPublicKey clientPublicKey, byte[] expectedToken) {
|
||||
try {
|
||||
if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) {
|
||||
Either<byte[], ?> either = packet.getSpecificModifier(Either.class).read(0);
|
||||
if (clientPublicKey == null) {
|
||||
Optional<byte[]> left = either.left();
|
||||
if (!left.isPresent()) {
|
||||
plugin.getLog().error("No verify token sent if requested without player signed key {}", sender);
|
||||
return false;
|
||||
}
|
||||
|
||||
return EncryptionUtil.verifyNonce(expectedToken, keyPair.getPrivate(), left.get());
|
||||
} else {
|
||||
Optional<?> optSignatureData = either.right();
|
||||
if (!optSignatureData.isPresent()) {
|
||||
plugin.getLog().error("No signature given to sent player signing key {}", sender);
|
||||
return false;
|
||||
}
|
||||
|
||||
Object signatureData = optSignatureData.get();
|
||||
long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true);
|
||||
byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true);
|
||||
|
||||
PublicKey publicKey = clientPublicKey.key();
|
||||
return EncryptionUtil.verifySignedNonce(expectedToken, publicKey, salt, signature);
|
||||
}
|
||||
} else {
|
||||
byte[] nonce = packet.getByteArrays().read(1);
|
||||
return EncryptionUtil.verifyNonce(expectedToken, keyPair.getPrivate(), nonce);
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchPaddingException
|
||||
| IllegalBlockSizeException | BadPaddingException signatureEx) {
|
||||
plugin.getLog().error("Invalid signature from player {}", sender, signatureEx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoginStart(PacketEvent packetEvent, Player player, String username) {
|
||||
private void onLogin(PacketEvent packetEvent, Player player) {
|
||||
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
|
||||
String sessionKey = player.getAddress().toString();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(player.getAddress());
|
||||
|
||||
//player.getName() won't work at this state
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
|
||||
if (packetEvent.getPacket().getMeta("original_name").isPresent()) {
|
||||
//username has been injected by ManualNameChange.java
|
||||
username = (String) packetEvent.getPacket().getMeta("original_name").get();
|
||||
}
|
||||
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
var profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
|
||||
.optionRead(0);
|
||||
|
||||
var clientKey = profileKey.flatMap(opt -> opt).flatMap(this::verifyPublicKey);
|
||||
if (verifyClientKeys && !clientKey.isPresent()) {
|
||||
// missing or incorrect
|
||||
// expired always not allowed
|
||||
player.kickPlayer(plugin.getCore().getMessage("invalid-public-key"));
|
||||
plugin.getLog().warn("Invalid public key from player {}", username);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
|
||||
|
||||
// packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable nameCheckTask = new NameCheckTask(
|
||||
plugin, random, player, packetEvent, username, clientKey.orElse(null), keyPair.getPublic()
|
||||
);
|
||||
// plugin.getScheduler().runAsync(nameCheckTask);
|
||||
nameCheckTask.run();
|
||||
}
|
||||
|
||||
private Optional<ClientPublicKey> verifyPublicKey(WrappedProfileKeyData profileKey) {
|
||||
Instant expires = profileKey.getExpireTime();
|
||||
PublicKey key = profileKey.getKey();
|
||||
byte[] signature = profileKey.getSignature();
|
||||
ClientPublicKey clientKey = ClientPublicKey.of(expires, key, signature);
|
||||
try {
|
||||
if (EncryptionUtil.verifyClientKey(clientKey, Instant.now())) {
|
||||
return Optional.of(clientKey);
|
||||
}
|
||||
} catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private String getUsername(PacketContainer packet) {
|
||||
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
|
||||
if (profile == null) {
|
||||
return packet.getStrings().read(0);
|
||||
}
|
||||
|
||||
//player.getName() won't work at this state
|
||||
return profile.getName();
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable nameCheckTask = new NameCheckTask(plugin, random, player, packetEvent, username, keyPair.getPublic());
|
||||
plugin.getScheduler().runAsync(nameCheckTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
@@ -48,22 +48,19 @@ class ProtocolLibLoginSource implements LoginSource {
|
||||
private final Player player;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private final ClientPublicKey clientKey;
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private final String serverId = "";
|
||||
private byte[] verifyToken;
|
||||
|
||||
ProtocolLibLoginSource(Player player, Random random, PublicKey serverPublicKey, ClientPublicKey clientKey) {
|
||||
public ProtocolLibLoginSource(Player player, Random random, PublicKey publicKey) {
|
||||
this.player = player;
|
||||
this.random = random;
|
||||
this.publicKey = serverPublicKey;
|
||||
this.clientKey = clientKey;
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableOnlinemode() {
|
||||
public void enableOnlinemode() throws InvocationTargetException {
|
||||
verifyToken = EncryptionUtil.generateVerifyToken(random);
|
||||
|
||||
/*
|
||||
@@ -91,7 +88,7 @@ class ProtocolLibLoginSource implements LoginSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
public void kick(String message) throws InvocationTargetException {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
|
||||
@@ -112,10 +109,6 @@ class ProtocolLibLoginSource implements LoginSource {
|
||||
return player.getAddress();
|
||||
}
|
||||
|
||||
public ClientPublicKey getClientKey() {
|
||||
return clientKey;
|
||||
}
|
||||
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
@@ -126,11 +119,11 @@ class ProtocolLibLoginSource implements LoginSource {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "player=" + player
|
||||
+ ", random=" + random
|
||||
+ ", serverId='" + serverId + '\''
|
||||
+ ", verifyToken=" + Arrays.toString(verifyToken)
|
||||
+ '}';
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"player=" + player +
|
||||
", random=" + random +
|
||||
", serverId='" + serverId + '\'' +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,15 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.craftapi.model.skin.Textures;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@@ -38,8 +41,13 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
public class SkinApplyListener implements Listener {
|
||||
|
||||
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
|
||||
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties");
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public SkinApplyListener(FastLoginBukkit plugin) {
|
||||
@@ -69,6 +77,16 @@ public class SkinApplyListener implements Listener {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
|
||||
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
|
||||
gameProfile.getProperties().put(Textures.KEY, skin);
|
||||
try {
|
||||
gameProfile.getProperties().put(Textures.KEY, skin);
|
||||
} catch (ClassCastException castException) {
|
||||
//Cauldron, MCPC, Thermos, ...
|
||||
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
|
||||
try {
|
||||
MethodUtils.invokeMethod(map, "put", new Object[]{Textures.KEY, skin.getHandle()});
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||
plugin.getLog().error("Error setting premium skin of: {}", player, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,28 +28,26 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import com.github.games647.craftapi.model.auth.Verification;
|
||||
import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.craftapi.resolver.AbstractResolver;
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
@@ -61,7 +59,6 @@ import java.util.UUID;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.START;
|
||||
@@ -73,9 +70,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
private static final Class<?> ENCRYPTION_CLASS;
|
||||
|
||||
static {
|
||||
ENCRYPTION_CLASS = MinecraftReflection.getMinecraftClass(
|
||||
"util." + ENCRYPTION_CLASS_NAME, ENCRYPTION_CLASS_NAME
|
||||
);
|
||||
ENCRYPTION_CLASS = MinecraftReflection.getMinecraftClass("util." + ENCRYPTION_CLASS_NAME, ENCRYPTION_CLASS_NAME);
|
||||
}
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
@@ -84,20 +79,16 @@ public class VerifyResponseTask implements Runnable {
|
||||
|
||||
private final Player player;
|
||||
|
||||
private final BukkitLoginSession session;
|
||||
|
||||
private final byte[] sharedSecret;
|
||||
|
||||
private static Method encryptMethod;
|
||||
private static Method cipherMethod;
|
||||
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent,
|
||||
Player player, BukkitLoginSession session,
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player,
|
||||
byte[] sharedSecret, KeyPair keyPair) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.player = player;
|
||||
this.session = session;
|
||||
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
|
||||
this.serverKey = keyPair;
|
||||
}
|
||||
@@ -105,14 +96,21 @@ public class VerifyResponseTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
verifyResponse(session);
|
||||
BukkitLoginSession session = plugin.getSession(player.getAddress());
|
||||
if (session == null) {
|
||||
disconnect("invalid-request",
|
||||
"GameProfile {0} tried to send encryption response at invalid state",
|
||||
player.getAddress());
|
||||
} else {
|
||||
verifyResponse(session);
|
||||
}
|
||||
} finally {
|
||||
//this is a fake packet; it shouldn't be sent to the server
|
||||
// synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
// }
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
// ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +126,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!enableEncryption(loginKey)) {
|
||||
if (!checkVerifyToken(session) || !enableEncryption(loginKey)) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@@ -145,56 +143,63 @@ public class VerifyResponseTask implements Runnable {
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
|
||||
if (response.isPresent()) {
|
||||
encryptConnection(session, requestedUsername, response.get());
|
||||
Verification verification = response.get();
|
||||
plugin.getLog().info("Profile {} has a verified premium account", requestedUsername);
|
||||
String realUsername = verification.getName();
|
||||
if (realUsername == null) {
|
||||
disconnect("invalid-session", "Username field null for {}", requestedUsername);
|
||||
return;
|
||||
}
|
||||
|
||||
SkinProperty[] properties = verification.getProperties();
|
||||
if (properties.length > 0) {
|
||||
session.setSkinProperty(properties[0]);
|
||||
}
|
||||
|
||||
session.setVerifiedUsername(realUsername);
|
||||
session.setUuid(verification.getId());
|
||||
session.setVerified(true);
|
||||
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(realUsername);
|
||||
} else {
|
||||
//user tried to fake an authentication
|
||||
disconnect(
|
||||
"invalid-session",
|
||||
"GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}",
|
||||
session.getRequestUsername(), socketAddress, serverId
|
||||
);
|
||||
disconnect("invalid-session", "GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}", session.getRequestUsername(), socketAddress, serverId);
|
||||
}
|
||||
} catch (IOException ioEx) {
|
||||
disconnect("error-kick", "Failed to connect to session server", ioEx);
|
||||
}
|
||||
}
|
||||
|
||||
private void encryptConnection(BukkitLoginSession session, String requestedUsername, Verification verification) {
|
||||
plugin.getLog().info("Profile {} has a verified premium account", requestedUsername);
|
||||
String realUsername = verification.getName();
|
||||
if (realUsername == null) {
|
||||
disconnect("invalid-session", "Username field null for {}", requestedUsername);
|
||||
return;
|
||||
}
|
||||
|
||||
SkinProperty[] properties = verification.getProperties();
|
||||
if (properties.length > 0) {
|
||||
session.setSkinProperty(properties[0]);
|
||||
}
|
||||
|
||||
session.setVerifiedUsername(realUsername);
|
||||
session.setUuid(verification.getId());
|
||||
session.setVerified(true);
|
||||
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(realUsername, session.getClientPublicKey());
|
||||
}
|
||||
|
||||
private void setPremiumUUID(UUID premiumUUID) {
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
||||
try {
|
||||
Object networkManager = getNetworkManager();
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
|
||||
|
||||
Class<?> managerClass = networkManager.getClass();
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(managerClass, "spoofedUUID", UUID.class);
|
||||
accessor.set(networkManager, premiumUUID);
|
||||
FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true);
|
||||
} catch (Exception exc) {
|
||||
plugin.getLog().error("Error setting premium uuid of {}", player, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVerifyToken(BukkitLoginSession session) throws GeneralSecurityException {
|
||||
byte[] requestVerify = session.getVerifyToken();
|
||||
//encrypted verify token
|
||||
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
||||
if (!Arrays.equals(requestVerify, EncryptionUtil.decrypt(serverKey.getPrivate(), responseVerify))) {
|
||||
//check if the verify-token are equal to the server sent one
|
||||
disconnect("invalid-verify-token",
|
||||
"GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}",
|
||||
session.getRequestUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to get the networkManager from ProtocolLib
|
||||
private Object getNetworkManager() throws IllegalAccessException, ClassNotFoundException {
|
||||
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
@@ -202,10 +207,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
// ChannelInjector
|
||||
Class<?> injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector");
|
||||
Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true);
|
||||
|
||||
Class<?> rawInjectorClass = rawInjector.getClass();
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(rawInjectorClass, "networkManager", Object.class);
|
||||
return accessor.get(rawInjector);
|
||||
return FieldUtils.readField(rawInjector, "networkManager", true);
|
||||
}
|
||||
|
||||
private boolean enableEncryption(SecretKey loginKey) throws IllegalArgumentException {
|
||||
@@ -217,15 +219,15 @@ public class VerifyResponseTask implements Runnable {
|
||||
try {
|
||||
// Try to get the old (pre MC 1.16.4) encryption method
|
||||
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
|
||||
.getMethodByParameters("a", SecretKey.class);
|
||||
.getMethodByParameters("a", SecretKey.class);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
// Get the new encryption method
|
||||
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
|
||||
.getMethodByParameters("a", Cipher.class, Cipher.class);
|
||||
.getMethodByParameters("a", Cipher.class, Cipher.class);
|
||||
|
||||
// Get the needed Cipher helper method (used to generate ciphers from login key)
|
||||
cipherMethod = FuzzyReflection.fromClass(ENCRYPTION_CLASS)
|
||||
.getMethodByParameters("a", int.class, Key.class);
|
||||
.getMethodByParameters("a", int.class, Key.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,35 +262,33 @@ public class VerifyResponseTask implements Runnable {
|
||||
private void kickPlayer(String reason) {
|
||||
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
|
||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
|
||||
//send kick packet at login state
|
||||
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
|
||||
ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket);
|
||||
//tell the server that we want to close the connection
|
||||
player.kickPlayer("Disconnect");
|
||||
try {
|
||||
//send kick packet at login state
|
||||
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
|
||||
ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket);
|
||||
//tell the server that we want to close the connection
|
||||
player.kickPlayer("Disconnect");
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLog().error("Error sending kick packet for: {}", player, ex);
|
||||
}
|
||||
}
|
||||
|
||||
//fake a new login packet in order to let the server handle all the other stuff
|
||||
private void receiveFakeStartPacket(String username, ClientPublicKey clientKey) {
|
||||
private void receiveFakeStartPacket(String username) {
|
||||
//see StartPacketListener for packet information
|
||||
PacketContainer startPacket = new PacketContainer(START);
|
||||
|
||||
if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) {
|
||||
startPacket.getStrings().write(0, username);
|
||||
|
||||
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
|
||||
val wrappedKey = Optional.ofNullable(clientKey).map(key ->
|
||||
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
|
||||
);
|
||||
|
||||
startPacket.getOptionals(converter).write(0, wrappedKey);
|
||||
} else {
|
||||
//uuid is ignored by the packet definition
|
||||
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
//uuid is ignored by the packet definition
|
||||
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
//we don't want to handle our own packets so ignore filters
|
||||
startPacket.setMeta(ProtocolLibListener.SOURCE_META_KEY, plugin.getName());
|
||||
ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, true);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLog().warn("Failed to fake a new start packet for: {}", username, ex);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
kickPlayer(plugin.getCore().getMessage("error-kick"));
|
||||
}
|
||||
|
||||
//we don't want to handle our own packets so ignore filters
|
||||
startPacket.setMeta(ProtocolLibListener.SOURCE_META_KEY, plugin.getName());
|
||||
ProtocolLibrary.getProtocolManager().receiveClientPacket(player, startPacket, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ public class ProtocolLoginSource implements LoginSource {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "loginStartEvent=" + loginStartEvent
|
||||
+ '}';
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"loginStartEvent=" + loginStartEvent +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,8 @@ import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
@@ -50,13 +49,13 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
||||
implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final AntiBotService antiBotService;
|
||||
private final RateLimiter rateLimiter;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.antiBotService = antiBotService;
|
||||
this.rateLimiter = rateLimiter;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -65,28 +64,19 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", loginStartEvent.getConnection());
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getConnection().getProfile().getName();
|
||||
InetSocketAddress address = loginStartEvent.getConnection().getRawAddress();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, address);
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
loginStartEvent.denyLogin(message);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(address);
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.removeSession(address);
|
||||
|
||||
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
|
||||
super.onLogin(username, source);
|
||||
break;
|
||||
}
|
||||
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
|
||||
super.onLogin(username, source);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -113,16 +103,15 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source,
|
||||
StoredProfile profile) {
|
||||
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source, StoredProfile profile) {
|
||||
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username,
|
||||
boolean registered) {
|
||||
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username
|
||||
, boolean registered) {
|
||||
source.enableOnlinemode();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.github.games647.fastlogin.bukkit.hook.CrazyLoginHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.LogItHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.LoginSecurityHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.UltraAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.XAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.xAuthHook;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -94,8 +94,8 @@ public class DelayedAuthHook implements Runnable {
|
||||
private AuthPlugin<Player> getAuthHook() {
|
||||
try {
|
||||
List<Class<? extends AuthPlugin<Player>>> hooks = Arrays.asList(AuthMeHook.class,
|
||||
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class,
|
||||
XAuthHook.class);
|
||||
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class,
|
||||
xAuthHook.class);
|
||||
|
||||
for (Class<? extends AuthPlugin<Player>> clazz : hooks) {
|
||||
String pluginName = clazz.getSimpleName();
|
||||
@@ -113,7 +113,7 @@ public class DelayedAuthHook implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected AuthPlugin<Player> newInstance(Class<? extends AuthPlugin<Player>> clazz)
|
||||
private AuthPlugin<Player> newInstance(Class<? extends AuthPlugin<Player>> clazz)
|
||||
throws ReflectiveOperationException {
|
||||
try {
|
||||
Constructor<? extends AuthPlugin<Player>> cons = clazz.getDeclaredConstructor(FastLoginBukkit.class);
|
||||
|
||||
@@ -25,11 +25,6 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.FloodgateManagement;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -38,10 +33,14 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.FloodgateManagement;
|
||||
|
||||
public class FloodgateAuthTask extends FloodgateManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
|
||||
|
||||
public FloodgateAuthTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player,
|
||||
FloodgatePlayer floodgatePlayer) {
|
||||
public FloodgateAuthTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player, FloodgatePlayer floodgatePlayer) {
|
||||
super(core, player, floodgatePlayer);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class FastLoginBukkitTest {
|
||||
|
||||
@Test
|
||||
void testRGB() {
|
||||
val message = "&x00002a00002b&lText";
|
||||
val msg = CommonUtil.translateColorCodes(message);
|
||||
assertEquals(msg, "§x00002a00002b§lText");
|
||||
|
||||
val components = TextComponent.fromLegacyText(msg);
|
||||
val expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
|
||||
assertEquals(ComponentSerializer.toString(components), expected);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
|
||||
import fr.xephi.authme.api.v3.AuthMeApi;
|
||||
import fr.xephi.authme.process.Management;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class ReflectionTest {
|
||||
|
||||
@Test
|
||||
void testAuthMeManagementField() {
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(AuthMeApi.class, Management.class, true);
|
||||
assertNotNull(accessor.getField());
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
|
||||
import de.st_ddt.crazylogin.CrazyLogin;
|
||||
import de.st_ddt.crazylogin.listener.PlayerListener;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class CrazyLoginHookTest {
|
||||
|
||||
@Test
|
||||
void testPlayerListener() {
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(CrazyLogin.class, PlayerListener.class, true);
|
||||
assertNotNull(accessor.getField());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
import lombok.val;
|
||||
|
||||
public class Base64Adapter extends TypeAdapter<byte[]> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, byte[] value) throws IOException {
|
||||
val encoded = Base64.getEncoder().encodeToString(value);
|
||||
out.value(encoded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(JsonReader in) throws IOException {
|
||||
String encoded = in.nextString();
|
||||
return Base64.getDecoder().decode(encoded);
|
||||
}
|
||||
}
|
||||
@@ -25,256 +25,22 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.SignatureTestData.SignatureData;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.hash.Hashing;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class EncryptionUtilTest {
|
||||
public class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testVerifyToken() {
|
||||
val random = ThreadLocalRandom.current();
|
||||
public void testVerifyToken() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] token = EncryptionUtil.generateVerifyToken(random);
|
||||
|
||||
assertAll(
|
||||
() -> assertNotNull(token),
|
||||
() -> assertEquals(token.length, 4)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServerKey() {
|
||||
KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
|
||||
Key privateKey = keyPair.getPrivate();
|
||||
assertEquals(privateKey.getAlgorithm(), "RSA");
|
||||
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
assertEquals(publicKey.getAlgorithm(), "RSA");
|
||||
|
||||
// clients accept larger values than the standard vanilla server, but we shouldn't crash them
|
||||
assertAll(
|
||||
() -> assertTrue(publicKey.getModulus().bitLength() >= 1024),
|
||||
() -> assertTrue(publicKey.getModulus().bitLength() < 8192)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExpiredClientKey() throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
|
||||
// Client expires at the exact second mentioned, so use it for verification
|
||||
val expiredTimestamp = clientKey.expiry();
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
// expiration date changed should make the signature invalid while still being not expired
|
||||
"client_keys/invalid_wrong_expiration.json",
|
||||
// changed public key no longer corresponding to the signature
|
||||
"client_keys/invalid_wrong_key.json",
|
||||
// signature modified no longer corresponding to key and expiration date
|
||||
"client_keys/invalid_wrong_signature.json"
|
||||
})
|
||||
void testInvalidClientKey(String clientKeySource) throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey(clientKeySource);
|
||||
Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidClientKey() throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecryptSharedSecret() throws Exception {
|
||||
KeyPair serverPair = EncryptionUtil.generateKeyPair();
|
||||
val serverPK = serverPair.getPublic();
|
||||
|
||||
SecretKey secretKey = generateSharedKey();
|
||||
byte[] encryptedSecret = encrypt(serverPK, secretKey.getEncoded());
|
||||
|
||||
SecretKey decryptSharedKey = EncryptionUtil.decryptSharedKey(serverPair.getPrivate(), encryptedSecret);
|
||||
assertEquals(decryptSharedKey, secretKey);
|
||||
}
|
||||
|
||||
private static byte[] encrypt(PublicKey receiverKey, byte... message)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||
IllegalBlockSizeException, BadPaddingException {
|
||||
val encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, receiverKey);
|
||||
return encryptCipher.doFinal(message);
|
||||
}
|
||||
|
||||
private static SecretKeySpec generateSharedKey() {
|
||||
// according to wiki.vg 16 bytes long
|
||||
byte[] sharedKey = new byte[16];
|
||||
ThreadLocalRandom.current().nextBytes(sharedKey);
|
||||
// shared key is to be used for the AES/CFB8 stream cipher to encrypt the traffic
|
||||
// therefore the encryption/decryption has to be AES
|
||||
return new SecretKeySpec(sharedKey, "AES");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServerIdHash() throws Exception {
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
assertEquals(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), sessionHash);
|
||||
}
|
||||
|
||||
private static String getServerHash(CharSequence serverId, SecretKey sharedSecret, PublicKey serverPK) {
|
||||
// https://wiki.vg/Protocol_Encryption#Client
|
||||
// sha1 := Sha1()
|
||||
// sha1.update(ASCII encoding of the server id string from Encryption Request)
|
||||
// sha1.update(shared secret)
|
||||
// sha1.update(server's encoded public key from Encryption Request)
|
||||
// hash := sha1.hexdigest() # String of hex characters
|
||||
@SuppressWarnings("deprecation")
|
||||
val hasher = Hashing.sha1().newHasher();
|
||||
hasher.putString(serverId, StandardCharsets.US_ASCII);
|
||||
hasher.putBytes(sharedSecret.getEncoded());
|
||||
hasher.putBytes(serverPK.getEncoded());
|
||||
// It works by treating the sha1 output bytes as one large integer in two's complement and then printing the
|
||||
// integer in base 16, placing a minus sign if the interpreted number is negative.
|
||||
// reference:
|
||||
// https://github.com/SpigotMC/BungeeCord/blob/ff5727c5ef9c0b56ad35f9816ae6bd660b622cf0/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java#L456
|
||||
return new BigInteger(hasher.hash().asBytes()).toString(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServerIdHashWrongSecret() throws Exception {
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
assertNotEquals(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), sessionHash);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServerIdHashWrongServerKey() {
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
val wrongPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
assertNotEquals(EncryptionUtil.getServerIdHashString("", sharedSecret, wrongPK), sessionHash);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidSignedNonce() throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json");
|
||||
assertTrue(verifySignedNonce(testData, clientKey));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"signature/incorrect_nonce.json",
|
||||
"signature/incorrect_salt.json",
|
||||
"signature/incorrect_signature.json",
|
||||
})
|
||||
void testIncorrectNonce(String signatureSource) throws Exception {
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
SignatureTestData testData = SignatureTestData.fromResource(signatureSource);
|
||||
assertFalse(verifySignedNonce(testData, clientKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongPublicKeySigned() throws Exception {
|
||||
// load a different public key
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json");
|
||||
SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json");
|
||||
assertFalse(verifySignedNonce(testData, clientKey));
|
||||
}
|
||||
|
||||
private static boolean verifySignedNonce(SignatureTestData testData, ClientPublicKey clientKey)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
PublicKey clientPublicKey = clientKey.key();
|
||||
|
||||
byte[] nonce = testData.getNonce();
|
||||
SignatureData signature = testData.getSignature();
|
||||
long salt = signature.getSalt();
|
||||
return EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonce() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
val encryptedNonce = encrypt(serverKey.getPublic(), expected);
|
||||
|
||||
assertTrue(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonceIncorrect() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
|
||||
// flipped first character
|
||||
val encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3, 4});
|
||||
assertFalse(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonceFailedDecryption() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
// generate a new keypair that is different
|
||||
val encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);
|
||||
|
||||
assertThrows(GeneralSecurityException.class,
|
||||
() -> EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonceIncorrectEmpty() {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
byte[] encryptedNonce = {};
|
||||
|
||||
assertThrows(GeneralSecurityException.class,
|
||||
() -> EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce)
|
||||
);
|
||||
assertThat(token, notNullValue());
|
||||
assertThat(token.length, is(4));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemReader;
|
||||
|
||||
public class ResourceLoader {
|
||||
|
||||
public static RSAPrivateKey parsePrivateKey(String keySpec)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
try (
|
||||
Reader reader = new StringReader(keySpec);
|
||||
PemReader pemReader = new PemReader(reader)
|
||||
) {
|
||||
PemObject pemObject = pemReader.readPemObject();
|
||||
byte[] content = pemObject.getContent();
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(content);
|
||||
|
||||
KeyFactory factory = KeyFactory.getInstance("RSA");
|
||||
return (RSAPrivateKey) factory.generatePrivate(privateKeySpec);
|
||||
}
|
||||
}
|
||||
|
||||
protected static ClientPublicKey loadClientKey(String path)
|
||||
throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
|
||||
URL keyUrl = Resources.getResource(path);
|
||||
|
||||
String lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
JsonObject object = new Gson().fromJson(lines, JsonObject.class);
|
||||
|
||||
Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString());
|
||||
String key = object.getAsJsonPrimitive("key").getAsString();
|
||||
RSAPublicKey publicKey = parsePublicKey(key);
|
||||
|
||||
byte[] signature = Base64.getDecoder().decode(object.getAsJsonPrimitive("signature").getAsString());
|
||||
return ClientPublicKey.of(expires, publicKey, signature);
|
||||
}
|
||||
|
||||
private static RSAPublicKey parsePublicKey(String keySpec)
|
||||
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
try (
|
||||
Reader reader = new StringReader(keySpec);
|
||||
PemReader pemReader = new PemReader(reader)
|
||||
) {
|
||||
PemObject pemObject = pemReader.readPemObject();
|
||||
byte[] content = pemObject.getContent();
|
||||
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
|
||||
|
||||
KeyFactory factory = KeyFactory.getInstance("RSA");
|
||||
return (RSAPublicKey) factory.generatePublic(pubKeySpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import lombok.val;
|
||||
|
||||
public class SignatureTestData {
|
||||
|
||||
public static SignatureTestData fromResource(String resourceName) throws IOException {
|
||||
val keyUrl = Resources.getResource(resourceName);
|
||||
val encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
|
||||
return new Gson().fromJson(encodedSignature, SignatureTestData.class);
|
||||
}
|
||||
|
||||
@JsonAdapter(Base64Adapter.class)
|
||||
private byte[] nonce;
|
||||
|
||||
private SignatureData signature;
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public SignatureData getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public static class SignatureData {
|
||||
|
||||
private long salt;
|
||||
|
||||
@JsonAdapter(Base64Adapter.class)
|
||||
private byte[] signature;
|
||||
|
||||
public long getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
class VerifyResponseTaskTest {
|
||||
|
||||
private static final String NETTY_INJECTOR_CLASS =
|
||||
"com.comphenix.protocol.injector.netty.channel.NettyChannelInjector";
|
||||
|
||||
@Test
|
||||
void getNetworkManagerReflection() throws ClassNotFoundException {
|
||||
try (
|
||||
MockedStatic<Bukkit> bukkitMock = mockStatic(Bukkit.class);
|
||||
MockedStatic<MinecraftReflection> reflectionMock = mockStatic(MinecraftReflection.class);
|
||||
MockedStatic<PacketRegistry> registryMock = mockStatic(PacketRegistry.class)
|
||||
) {
|
||||
bukkitMock.when(Bukkit::getVersion).thenReturn("git-Bukkit-18fbb24 (MC: 1.17)");
|
||||
reflectionMock.when(MinecraftReflection::getMinecraftPackage).thenReturn("xyz");
|
||||
reflectionMock.when(MinecraftReflection::getEnumProtocolClass).thenReturn(Object.class);
|
||||
|
||||
registryMock.when(() -> PacketRegistry.tryGetPacketClass(any())).thenReturn(Optional.empty());
|
||||
|
||||
|
||||
Class<?> injectorClass = Class.forName(NETTY_INJECTOR_CLASS);
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(injectorClass, "networkManager", Object.class);
|
||||
assertNotNull(accessor.getField());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class DelayedAuthHookTest {
|
||||
|
||||
@Test
|
||||
void createNewReflectiveInstance() throws ReflectiveOperationException {
|
||||
val authHook = new DelayedAuthHook(null);
|
||||
assertNotNull(authHook.newInstance(DummyHook.class));
|
||||
}
|
||||
|
||||
public static class DummyHook implements AuthPlugin<Player> {
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
# Integration tests for authentication
|
||||
|
||||
## Description
|
||||
|
||||
Projects require integration tests in order to check against errors that could only occur if connected to other
|
||||
components. However, they are heavier in terms of performance and require a more complex setup. Unit tests often make
|
||||
use of fake, mock, stubs, etc. implementations to test the unit in isolation and thus could hide issues across
|
||||
boundaries of a unit. Nevertheless, both are not replacement for each other.
|
||||
|
||||
## Usage in this project
|
||||
|
||||
The authentication system is a core component, so it requires some kind of testing. Here we are going to
|
||||
spin up a Spigot server and test with the supported authentication schemes against the implementation of MCProtocolLib.
|
||||
|
||||
### Goals
|
||||
|
||||
* OS platform independent
|
||||
* Reproducible, but not fixed to a specific image hash
|
||||
* This is a dev container, so fixing it to feature/major version is enough instead of a version fixed by hash
|
||||
* Improve container spin up
|
||||
* E.g. Remove/Reduce world generation
|
||||
|
||||
### Note on automation
|
||||
|
||||
The simplest solution it to use the official Mojang session and authentication servers. However, this would require
|
||||
a spare Minecraft account. Mocking the auth servers would be a solution to avoid this.
|
||||
|
||||
## Related
|
||||
|
||||
Interest blog article about integration tests and why they are necessary.
|
||||
https://software.rajivprab.com/2019/04/28/rethinking-software-testing-perspectives-from-the-world-of-hardware/
|
||||
|
||||
## Issues
|
||||
|
||||
### Slow startup
|
||||
|
||||
Tried a lot of optimizations like only loading a single world without the nether or the end. However, there the startup
|
||||
is still slow. If you have any ideas on how to tune the startup parameters of the Minecraft server or the JVM
|
||||
itself to reduce the startup time, please suggest it.
|
||||
|
||||
### Checkpoint
|
||||
|
||||
An idea to optimize the time is to use CRIU (checkpoint and restore). So to save the process at a certain stage and
|
||||
restore all data multiple times. This could cause a lot of issues like open files have to be present. However, the
|
||||
impact is significant and since it runs inside the container all files, pids (pid=1) should be matching. Potential
|
||||
checkpoint locations are:
|
||||
|
||||
* Direct before loading the plugins
|
||||
* Likely before binding the port to prevent issues
|
||||
* After loading the libraries
|
||||
|
||||
Nevertheless, the current state requires to run it with root and the Java support is currently still in progress.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# About
|
||||
|
||||
This contains test resources for the unit tests. The files are extracted from the Minecraft directory with slight
|
||||
modifications. The files are found in `$MINECRAFT_HOME$/profilekeys/`, where `$MINECRAFT_HOME$` represents the
|
||||
OS-dependent minecraft folder.
|
||||
|
||||
**Notable the files in this folder do not contain the private key information. It should be explicitly
|
||||
stripped before including it.**
|
||||
|
||||
## Minecraft folder
|
||||
|
||||
* Windows: `%appdata%\.minecraft`
|
||||
* Linux: `/home/username/.minecraft`
|
||||
* Mac: `~/Library/Application Support/minecraft`
|
||||
|
||||
## Directory structure
|
||||
|
||||
* `valid_public_key.json`: Extracted from the actual file
|
||||
* `invalid_wrong_expiration.json`: Changed the expiration date
|
||||
* `invalid_wrong_key.json`: Modified public key while keeping the RSA structure valid
|
||||
* `invalid_wrong_signature.json`: Changed a character in the public key signature
|
||||
|
||||
## File content
|
||||
|
||||
* `expires_at`: Expiration date
|
||||
* `key`: Public key from the original file out of `public_key.key`
|
||||
* `signature`: Mojang signed signature of this public key
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"expires_at": "2022-06-20T07:31:47.318722344Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd3ZxDhcRWWru1XEBke6uYqmbnS2Oxyk\nOMj+QDKrkwUqhVJYciyXGsMx46Mgr/KIoGCcokP5OtIxc6+69/ZLqJ9PvM81kLIxAqyvfBMKMGjP\n376LgxTF1FeDpbe5zXaNRxfmnvQhS5YTLbzgk36qWVjqxJMG4VLVmh7RV5zWsb7XlckZb2zRHM2Y\nMHbEC+ggX+l6zQyfG1KK0MH5k+O6b0xD0rv1wm24sLOesTXH6RZG8cNE3ofdnavxjFodTOnra6w1\naiVcoUTdEPSS86wQwq9j0YCcAKOwMXsqbk9NhpujrdyJ94dev+ELwkNS7P0pPrcfiyFTQeJCZTXz\nJB36MwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "lfRXK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU="
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"expires_at": "2022-06-20T08:31:47.318722344Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU3I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "lfRXK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU="
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"expires_at": "2022-06-20T08:31:47.318722344Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd3ZxDhcRWWru1XEBke6uYqmbnS2Oxyk\nOMj+QDKrkwUqhVJYciyXGsMx46Mgr/KIoGCcokP5OtIxc6+69/ZLqJ9PvM81kLIxAqyvfBMKMGjP\n376LgxTF1FeDpbe5zXaNRxfmnvQhS5YTLbzgk36qWVjqxJMG4VLVmh7RV5zWsb7XlckZb2zRHM2Y\nMHbEC+ggX+l6zQyfG1KK0MH5k+O6b0xD0rv1wm24sLOesTXH6RZG8cNE3ofdnavxjFodTOnra6w1\naiVcoUTdEPSS86wQwq9j0YCcAKOwMXsqbk9NhpujrdyJ94dev+ELwkNS7P0pPrcfiyFTQeJCZTXz\nJB36MwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "lfRxK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU="
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"expires_at": "2022-06-20T08:31:47.318722344Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd3ZxDhcRWWru1XEBke6uYqmbnS2Oxyk\nOMj+QDKrkwUqhVJYciyXGsMx46Mgr/KIoGCcokP5OtIxc6+69/ZLqJ9PvM81kLIxAqyvfBMKMGjP\n376LgxTF1FeDpbe5zXaNRxfmnvQhS5YTLbzgk36qWVjqxJMG4VLVmh7RV5zWsb7XlckZb2zRHM2Y\nMHbEC+ggX+l6zQyfG1KK0MH5k+O6b0xD0rv1wm24sLOesTXH6RZG8cNE3ofdnavxjFodTOnra6w1\naiVcoUTdEPSS86wQwq9j0YCcAKOwMXsqbk9NhpujrdyJ94dev+ELwkNS7P0pPrcfiyFTQeJCZTXz\nJB36MwIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "lfRXK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU="
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# About
|
||||
|
||||
This contains test resources for the unit tests. Files in this folder include pre-made cryptographic signatures.
|
||||
|
||||
## Directory structure
|
||||
|
||||
* `valid_signature.json`: Extracted using packet extract from an actual authentication
|
||||
* `incorrect_nonce.json`: Different nonce token simulating that the server expected a different token than signed
|
||||
* `incorrect_salt.json`: Salt sent is different to the content signed by the signature (changed salt field)
|
||||
* `incorrect_signature.json`: Changed signature
|
||||
|
||||
## File content
|
||||
|
||||
* `nonce`: Server generated nonce token
|
||||
* `salt`: Client generated random token that will be signed
|
||||
* `signature`: Nonce and salt signed using the client key from `valid_public_key.json`
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"nonce": "galNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -2985008842905108412
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"nonce": "GalNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -1985008842905108412
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"nonce": "GalNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "jlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -2985008842905108412
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"nonce": "GalNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -2985008842905108412
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -46,11 +46,10 @@
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<artifactSet>
|
||||
@@ -138,30 +137,6 @@
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-native</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-query</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-slf4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.maven.resolver</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
@@ -217,7 +192,7 @@
|
||||
<dependency>
|
||||
<groupId>de.xxschrandxx.bca</groupId>
|
||||
<artifactId>BungeeCordAuthenticator</artifactId>
|
||||
<version>0.0.3</version>
|
||||
<version>0.0.2</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
||||
@@ -59,10 +59,10 @@ public class BungeeLoginSession extends LoginSession {
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "alreadySaved=" + alreadySaved
|
||||
+ ", alreadyLogged=" + alreadyLogged
|
||||
+ ", registered=" + registered
|
||||
+ "} " + super.toString();
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"alreadySaved=" + alreadySaved +
|
||||
", alreadyLogged=" + alreadyLogged +
|
||||
", registered=" + registered +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ public class BungeeLoginSource implements LoginSource {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "connection=" + connection
|
||||
+ '}';
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"connection=" + connection +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
|
||||
@@ -101,7 +100,7 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
|
||||
//events
|
||||
PluginManager pluginManager = getProxy().getPluginManager();
|
||||
|
||||
Listener connectListener = new ConnectListener(this, core.getAntiBot());
|
||||
ConnectListener connectListener = new ConnectListener(this, core.getRateLimiter());
|
||||
pluginManager.registerListener(this, connectListener);
|
||||
pluginManager.registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ package com.github.games647.fastlogin.bungee.event;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ package com.github.games647.fastlogin.bungee.event;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
public class BungeeFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
|
||||
|
||||
@@ -34,13 +34,11 @@ import me.vik1395.BungeeAuthAPI.RequestHandler;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* GitHub:
|
||||
* <a href="https://github.com/vik1395/BungeeAuth-Minecraft">...</a>
|
||||
* GitHub: https://github.com/vik1395/BungeeAuth-Minecraft
|
||||
*
|
||||
* Project page:
|
||||
*
|
||||
* Spigot:
|
||||
* <a href="https://www.spigotmc.org/resources/bungeeauth.493/">...</a>
|
||||
* Spigot: https://www.spigotmc.org/resources/bungeeauth.493/
|
||||
*/
|
||||
public class BungeeAuthHook implements AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
|
||||
@@ -38,11 +38,11 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* GitHub:
|
||||
* <a href="https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator">...</a>
|
||||
* https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Spigot: <a href="https://www.spigotmc.org/resources/bungeecordauthenticator.87669/">...</a>
|
||||
* Spigot: https://www.spigotmc.org/resources/bungeecordauthenticator.87669/
|
||||
*/
|
||||
public class BungeeCordAuthenticatorBungeeHook implements AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@ import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck;
|
||||
import com.github.games647.fastlogin.bungee.task.FloodgateAuthTask;
|
||||
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.google.common.base.Throwables;
|
||||
@@ -42,10 +41,8 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
@@ -71,7 +68,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class ConnectListener implements Listener {
|
||||
|
||||
private static final String UUID_FIELD_NAME = "uniqueId";
|
||||
protected static final MethodHandle UNIQUE_ID_SETTER;
|
||||
private static final MethodHandle uniqueIdSetter;
|
||||
|
||||
static {
|
||||
MethodHandle setHandle = null;
|
||||
@@ -87,20 +84,20 @@ public class ConnectListener implements Listener {
|
||||
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
|
||||
logger.error(
|
||||
"Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.",
|
||||
reflectiveOperationException
|
||||
reflectiveOperationException
|
||||
);
|
||||
}
|
||||
|
||||
UNIQUE_ID_SETTER = setHandle;
|
||||
uniqueIdSetter = setHandle;
|
||||
}
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final AntiBotService antiBotService;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final Property[] emptyProperties = {};
|
||||
|
||||
public ConnectListener(FastLoginBungee plugin, AntiBotService antiBotService) {
|
||||
public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter) {
|
||||
this.plugin = plugin;
|
||||
this.antiBotService = antiBotService;
|
||||
this.rateLimiter = rateLimiter;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -110,28 +107,17 @@ public class ConnectListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
InetSocketAddress address = preLoginEvent.getConnection().getAddress();
|
||||
String username = connection.getName();
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection);
|
||||
return;
|
||||
}
|
||||
|
||||
String username = connection.getName();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
preLoginEvent.setCancelReason(TextComponent.fromLegacyText(message));
|
||||
preLoginEvent.setCancelled(true);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
break;
|
||||
}
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@@ -154,8 +140,8 @@ public class ConnectListener implements Listener {
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
playerProfile.setId(verifiedUUID);
|
||||
|
||||
// BungeeCord will do this automatically so override it on disabled option
|
||||
if (UNIQUE_ID_SETTER != null) {
|
||||
// bungeecord will do this automatically so override it on disabled option
|
||||
if (uniqueIdSetter != null) {
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
|
||||
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
|
||||
@@ -171,7 +157,7 @@ public class ConnectListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setOfflineId(InitialHandler connection, String username) {
|
||||
private void setOfflineId(InitialHandler connection, String username) {
|
||||
try {
|
||||
UUID oldPremiumId = connection.getUniqueId();
|
||||
UUID offlineUUID = UUIDAdapter.generateOfflineId(username);
|
||||
@@ -179,7 +165,7 @@ public class ConnectListener implements Listener {
|
||||
// BungeeCord only allows setting the UUID in PreLogin events and before requesting online mode
|
||||
// However if online mode is requested, it will override previous values
|
||||
// So we have to do it with reflection
|
||||
UNIQUE_ID_SETTER.invokeExact(connection, offlineUUID);
|
||||
uniqueIdSetter.invokeExact(connection, offlineUUID);
|
||||
|
||||
String format = "Overridden UUID from {} to {} (based of {}) on {}";
|
||||
plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection);
|
||||
|
||||
@@ -29,8 +29,8 @@ import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPremiumToggleEvent;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
@@ -73,8 +73,8 @@ public class AsyncToggleMessage implements Runnable {
|
||||
playerProfile.setPremium(false);
|
||||
playerProfile.setId(null);
|
||||
core.getStorage().save(playerProfile);
|
||||
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName()))
|
||||
? PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
|
||||
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getPluginManager().callEvent(
|
||||
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
sendMessage("remove-premium");
|
||||
@@ -89,8 +89,8 @@ public class AsyncToggleMessage implements Runnable {
|
||||
|
||||
playerProfile.setPremium(true);
|
||||
core.getStorage().save(playerProfile);
|
||||
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName()))
|
||||
? PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
|
||||
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getPluginManager().callEvent(
|
||||
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
sendMessage("add-premium");
|
||||
|
||||
@@ -25,19 +25,19 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bungee.task;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.FloodgateManagement;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.FloodgateManagement;
|
||||
|
||||
public class FloodgateAuthTask
|
||||
extends FloodgateManagement<ProxiedPlayer, CommandSender, BungeeLoginSession, FastLoginBungee> {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.conf.Configuration;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class ConnectListenerTest {
|
||||
|
||||
@Test
|
||||
void testUUIDSetter() throws Throwable {
|
||||
BungeeCord proxyMock = mock(BungeeCord.class);
|
||||
BungeeCord.setInstance(proxyMock);
|
||||
|
||||
Configuration configMock = mock(Configuration.class);
|
||||
Field configField = proxyMock.getClass().getField("config");
|
||||
configField.setAccessible(true);
|
||||
configField.set(proxyMock, configMock);
|
||||
|
||||
InitialHandler handler = new InitialHandler(proxyMock, null);
|
||||
|
||||
UUID expectedUUID = UUID.randomUUID();
|
||||
ConnectListener.UNIQUE_ID_SETTER.invokeExact(handler, expectedUUID);
|
||||
assertEquals(expectedUUID, handler.getUniqueId());
|
||||
}
|
||||
}
|
||||
224
checkstyle.xml
224
checkstyle.xml
@@ -1,224 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2022 games647 and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-->
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!--
|
||||
|
||||
Checkstyle configuration that checks the sun coding conventions from:
|
||||
|
||||
- the Java Language Specification at
|
||||
https://docs.oracle.com/javase/specs/jls/se11/html/index.html
|
||||
|
||||
- the Sun Code Conventions at https://www.oracle.com/java/technologies/javase/codeconventions-contents.html
|
||||
|
||||
- the Javadoc guidelines at
|
||||
https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html
|
||||
|
||||
- the JDK Api documentation https://docs.oracle.com/en/java/javase/11/
|
||||
|
||||
- some best practices
|
||||
|
||||
Checkstyle is very configurable. Be sure to read the documentation at
|
||||
https://checkstyle.org (or in your downloaded distribution).
|
||||
|
||||
Most Checks are configurable, be sure to consult the documentation.
|
||||
|
||||
To completely disable a check, just comment it out or delete it from the file.
|
||||
To suppress certain violations please review suppression filters.
|
||||
|
||||
Finally, it is worth reading the documentation.
|
||||
|
||||
-->
|
||||
|
||||
<module name="Checker">
|
||||
<!--
|
||||
If you set the basedir property below, then all reported file
|
||||
names will be relative to the specified directory. See
|
||||
https://checkstyle.org/config.html#Checker
|
||||
|
||||
<property name="basedir" value="${basedir}"/>
|
||||
-->
|
||||
<property name="severity" value="error"/>
|
||||
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
|
||||
<!-- Excludes all 'module-info.java' files -->
|
||||
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
|
||||
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${org.checkstyle.sun.suppressionfilter.config}"
|
||||
default="checkstyle-suppressions.xml" />
|
||||
<property name="optional" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks that a package-info.java file exists for each package. -->
|
||||
<!-- See https://checkstyle.org/config_javadoc.html#JavadocPackage -->
|
||||
<!--<module name="JavadocPackage"/>-->
|
||||
|
||||
<!-- Checks whether files end with a new line. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
|
||||
<!-- Checks that property files contain the same keys. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html#Translation -->
|
||||
<module name="Translation"/>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See https://checkstyle.org/config_sizes.html -->
|
||||
<module name="FileLength"/>
|
||||
<module name="LineLength">
|
||||
<property name="max" value="120"/>
|
||||
<property name="fileExtensions" value="java"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See https://checkstyle.org/config_whitespace.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Headers -->
|
||||
<!-- See https://checkstyle.org/config_header.html -->
|
||||
<!-- <module name="Header"> -->
|
||||
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
|
||||
<!-- <property name="fileExtensions" value="java"/> -->
|
||||
<!-- </module> -->
|
||||
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See https://checkstyle.org/config_javadoc.html -->
|
||||
<module name="InvalidJavadocPosition"/>
|
||||
<module name="JavadocMethod"/>
|
||||
<!--<module name="JavadocType"/>-->
|
||||
<!--<module name="JavadocVariable"/>-->
|
||||
<!--<module name="JavadocStyle"/>-->
|
||||
<!--<module name="MissingJavadocMethod"/>-->
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
<!-- See https://checkstyle.org/config_naming.html -->
|
||||
<module name="ConstantName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="MemberName"/>
|
||||
<module name="MethodName"/>
|
||||
<module name="PackageName"/>
|
||||
<module name="ParameterName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="TypeName"/>
|
||||
|
||||
<!-- Checks for imports -->
|
||||
<!-- See https://checkstyle.org/config_imports.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports">
|
||||
<property name="processJavadoc" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See https://checkstyle.org/config_sizes.html -->
|
||||
<module name="MethodLength"/>
|
||||
<module name="ParameterNumber"/>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See https://checkstyle.org/config_whitespace.html -->
|
||||
<module name="EmptyForIteratorPad"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceAfter"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
<!-- Modifier Checks -->
|
||||
<!-- See https://checkstyle.org/config_modifier.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<!-- Checks for blocks. You know, those {}'s -->
|
||||
<!-- See https://checkstyle.org/config_blocks.html -->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
<module name="EmptyBlock"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="NeedBraces"/>
|
||||
<module name="RightCurly"/>
|
||||
|
||||
<!-- Checks for common coding problems -->
|
||||
<!-- See https://checkstyle.org/config_coding.html -->
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="IllegalInstantiation"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<!--<module name="MagicNumber"/>-->
|
||||
<module name="MissingSwitchDefault"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
|
||||
<!-- Checks for class design -->
|
||||
<!-- See https://checkstyle.org/config_design.html -->
|
||||
<!--<module name="DesignForExtension"/>-->
|
||||
<module name="FinalClass"/>
|
||||
<module name="HideUtilityClassConstructor"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<!--<module name="FinalParameters"/>-->
|
||||
<!-- <module name="TodoComment"/>-->
|
||||
<module name="UpperEll"/>
|
||||
|
||||
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
|
||||
<module name="SuppressionXpathFilter">
|
||||
<property name="file" value="${org.checkstyle.sun.suppressionxpathfilter.config}"
|
||||
default="checkstyle-xpath-suppressions.xml" />
|
||||
<property name="optional" value="true"/>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -32,7 +32,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
<repository>
|
||||
<id>codemc-repo</id>
|
||||
<url>https://repo.codemc.io/repository/maven-public/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<!-- Floodgate -->
|
||||
<repository>
|
||||
@@ -92,7 +95,7 @@
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-config</artifactId>
|
||||
<version>1.19-R0.1-20220702.004052-16</version>
|
||||
<version>1.16-R0.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
|
||||
@@ -58,18 +58,16 @@ public class AsyncScheduler {
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> runAsync(Runnable task) {
|
||||
return CompletableFuture.runAsync(() -> process(task), processingPool).exceptionally(error -> {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
currentlyRunning.incrementAndGet();
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
currentlyRunning.getAndDecrement();
|
||||
}
|
||||
}, processingPool).exceptionally(error -> {
|
||||
logger.warn("Error occurred on thread pool", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void process(Runnable task) {
|
||||
currentlyRunning.incrementAndGet();
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
currentlyRunning.getAndDecrement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.github.games647.craftapi.cache.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
@@ -37,13 +37,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.jul.JDK14LoggerAdapter;
|
||||
|
||||
public final class CommonUtil {
|
||||
public class CommonUtil {
|
||||
|
||||
private static final char COLOR_CHAR = '&';
|
||||
private static final char TRANSLATED_CHAR = '§';
|
||||
|
||||
public static <K, V> ConcurrentMap<K, V> buildCache(int expireAfterWrite, int maxSize) {
|
||||
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
|
||||
SafeCacheBuilder<Object, Object> builder = SafeCacheBuilder.newBuilder();
|
||||
|
||||
if (expireAfterWrite > 0) {
|
||||
builder.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES);
|
||||
@@ -53,13 +53,15 @@ public final class CommonUtil {
|
||||
builder.maximumSize(maxSize);
|
||||
}
|
||||
|
||||
return builder.<K, V>build().asMap();
|
||||
return builder.build(CacheLoader.from(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
}));
|
||||
}
|
||||
|
||||
public static String translateColorCodes(String rawMessage) {
|
||||
char[] chars = rawMessage.toCharArray();
|
||||
for (int i = 0; i < chars.length - 1; i++) {
|
||||
if (chars[i] == COLOR_CHAR && "x0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(chars[i + 1]) > -1) {
|
||||
if (chars[i] == COLOR_CHAR && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(chars[i + 1]) > -1) {
|
||||
chars[i] = TRANSLATED_CHAR;
|
||||
chars[i + 1] = Character.toLowerCase(chars[i + 1]);
|
||||
}
|
||||
@@ -72,7 +74,7 @@ public final class CommonUtil {
|
||||
* This creates a SLF4J logger. In the process it initializes the SLF4J service provider. This method looks
|
||||
* for the provider in the plugin jar instead of in the server jar when creating a Logger. The provider is only
|
||||
* initialized once, so this method should be called early.
|
||||
* <p>
|
||||
*
|
||||
* The provider is bound to the service class `SLF4JServiceProvider`. Relocating this class makes it available
|
||||
* for exclusive own usage. Other dependencies will use the relocated service too, and therefore will find the
|
||||
* initialized provider.
|
||||
@@ -91,9 +93,12 @@ public final class CommonUtil {
|
||||
LoggerFactory.getLogger(parent.getName()).info("Initialize logging service");
|
||||
try {
|
||||
parent.setLevel(Level.ALL);
|
||||
return createJDKLogger(parent);
|
||||
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
|
||||
| NoSuchMethodException reflectEx) {
|
||||
|
||||
Class<JDK14LoggerAdapter> adapterClass = JDK14LoggerAdapter.class;
|
||||
Constructor<JDK14LoggerAdapter> cons = adapterClass.getDeclaredConstructor(java.util.logging.Logger.class);
|
||||
cons.setAccessible(true);
|
||||
return cons.newInstance(parent);
|
||||
} catch (ReflectiveOperationException reflectEx) {
|
||||
parent.log(Level.WARNING, "Cannot create slf4j logging adapter", reflectEx);
|
||||
parent.log(Level.WARNING, "Creating logger instance manually...");
|
||||
return LoggerFactory.getLogger(parent.getName());
|
||||
@@ -103,15 +108,7 @@ public final class CommonUtil {
|
||||
}
|
||||
}
|
||||
|
||||
protected static JDK14LoggerAdapter createJDKLogger(java.util.logging.Logger parent)
|
||||
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
Class<JDK14LoggerAdapter> adapterClass = JDK14LoggerAdapter.class;
|
||||
Constructor<JDK14LoggerAdapter> cons = adapterClass.getDeclaredConstructor(java.util.logging.Logger.class);
|
||||
cons.setAccessible(true);
|
||||
return cons.newInstance(parent);
|
||||
}
|
||||
|
||||
private CommonUtil() {
|
||||
throw new RuntimeException("No instantiation of utility classes allowed");
|
||||
//Utility class
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core.antibot;
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RateLimiter {
|
||||
@@ -114,21 +114,11 @@ public class StoredProfile extends Profile {
|
||||
|
||||
@Override
|
||||
public synchronized boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof StoredProfile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StoredProfile that = (StoredProfile) o;
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof StoredProfile that)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
return rowId == that.rowId && premium == that.premium
|
||||
&& Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin);
|
||||
&& Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,11 +128,11 @@ public class StoredProfile extends Profile {
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "rowId=" + rowId
|
||||
+ ", premium=" + premium
|
||||
+ ", lastIp='" + lastIp + '\''
|
||||
+ ", lastLogin=" + lastLogin
|
||||
+ "} " + super.toString();
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"rowId=" + rowId +
|
||||
", premium=" + premium +
|
||||
", lastIp='" + lastIp + '\'' +
|
||||
", lastLogin=" + lastLogin +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core.antibot;
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.google.common.base.Ticker;
|
||||
|
||||
@@ -113,7 +113,7 @@ public class TickingRateLimiter implements RateLimiter {
|
||||
private final long expireTime;
|
||||
private int count;
|
||||
|
||||
TimeRecord(long firstMinuteRecord, long expireTime) {
|
||||
public TimeRecord(long firstMinuteRecord, long expireTime) {
|
||||
this.firstMinuteRecord = firstMinuteRecord;
|
||||
this.expireTime = expireTime;
|
||||
this.count = 1;
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core.antibot;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class AntiBotService {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private final RateLimiter rateLimiter;
|
||||
private final Action limitReachedAction;
|
||||
|
||||
public AntiBotService(Logger logger, RateLimiter rateLimiter, Action limitReachedAction) {
|
||||
this.logger = logger;
|
||||
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.limitReachedAction = limitReachedAction;
|
||||
}
|
||||
|
||||
public Action onIncomingConnection(InetSocketAddress clientAddress, String username) {
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
logger.warn("Anti-Bot join limit - Ignoring {}", clientAddress);
|
||||
return limitReachedAction;
|
||||
}
|
||||
|
||||
return Action.Continue;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
Ignore,
|
||||
|
||||
Block,
|
||||
|
||||
Continue;
|
||||
}
|
||||
}
|
||||
@@ -74,12 +74,12 @@ public abstract class BedrockService<B> {
|
||||
"Could not check whether Bedrock Player {}'s name conflicts a premium Java player's name.",
|
||||
username);
|
||||
|
||||
kickPlayer(source, username, "Could not check if your name conflicts an existing "
|
||||
+ "premium Java account's name. This is usually a serverside error.");
|
||||
kickPlayer(source, username, "Could not check if your name conflicts an existing " +
|
||||
"premium Java account's name. This is usually a serverside error.");
|
||||
} catch (RateLimitException rateLimitException) {
|
||||
core.getPlugin().getLog().warn("Mojang API rate limit hit");
|
||||
kickPlayer(source, username, "Could not check if your name conflicts an existing premium "
|
||||
+ "Java account's name. Try again in a few minutes");
|
||||
kickPlayer(source, username, "Could not check if your name conflicts an existing premium " +
|
||||
"Java account's name. Try again in a few minutes");
|
||||
}
|
||||
|
||||
if (premiumUUID.isPresent()) {
|
||||
|
||||
@@ -54,22 +54,19 @@ public class FloodgateService extends BedrockService<FloodgatePlayer> {
|
||||
* <li>autoLoginFloodgate
|
||||
* <li>autoRegisterFloodgate
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param key the key of the entry in config.yml
|
||||
* @return <b>true</b> if the entry's value is "true", "false", or "linked"
|
||||
*/
|
||||
public boolean isValidFloodgateConfigString(String key) {
|
||||
String value = core.getConfig().get(key).toString().toLowerCase(Locale.ENGLISH);
|
||||
switch (value) {
|
||||
case "true":
|
||||
case "linked":
|
||||
case "false":
|
||||
case "no-conflict":
|
||||
return true;
|
||||
default:
|
||||
core.getPlugin().getLog().error("Invalid value detected for {} in FastLogin/config.yml.", key);
|
||||
return false;
|
||||
if (!value.equals("true") && !value.equals("linked") && !value.equals("false") && !value.equals("no-conflict")) {
|
||||
core.getPlugin().getLog().error("Invalid value detected for {} in FastLogin/config.yml.", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,11 +83,11 @@ public class FloodgateService extends BedrockService<FloodgatePlayer> {
|
||||
|
||||
if ("false".equals(allowConflict)
|
||||
|| "linked".equals(allowConflict) && !isLinked) {
|
||||
super.checkNameConflict(username, source);
|
||||
super.checkNameConflict(username, source);
|
||||
} else {
|
||||
core.getPlugin().getLog().info("Skipping name conflict checking for player {}", username);
|
||||
}
|
||||
|
||||
|
||||
//Floodgate users don't need Java specific checks
|
||||
return true;
|
||||
}
|
||||
@@ -101,7 +98,7 @@ public class FloodgateService extends BedrockService<FloodgatePlayer> {
|
||||
* username can be found
|
||||
* <br>
|
||||
* <i>Falls back to non-prefixed name checks, if ProtocolLib is installed</i>
|
||||
*
|
||||
*
|
||||
* @param prefixedUsername the name of the player with the prefix appended
|
||||
* @return FloodgatePlayer if found, null otherwise
|
||||
*/
|
||||
|
||||
@@ -79,10 +79,10 @@ public class ChangePremiumMessage implements ChannelMessage {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "playerName='" + playerName + '\''
|
||||
+ ", shouldEnable=" + willEnable
|
||||
+ ", isSourceInvoker=" + isSourceInvoker
|
||||
+ '}';
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"playerName='" + playerName + '\'' +
|
||||
", shouldEnable=" + willEnable +
|
||||
", isSourceInvoker=" + isSourceInvoker +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,11 @@ public class LoginActionMessage implements ChannelMessage {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "type='" + type + '\''
|
||||
+ ", playerName='" + playerName + '\''
|
||||
+ ", proxyId=" + proxyId
|
||||
+ '}';
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"type='" + type + '\'' +
|
||||
", playerName='" + playerName + '\'' +
|
||||
", proxyId=" + proxyId +
|
||||
'}';
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
|
||||
@@ -23,16 +23,3 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CommonUtilTest {
|
||||
|
||||
@Test
|
||||
void createJDKLogger() throws Exception {
|
||||
CommonUtil.createJDKLogger(Logger.getAnonymousLogger());
|
||||
}
|
||||
}
|
||||
@@ -36,46 +36,42 @@ import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An extension to {@link MojangResolver} which allows connection using transparent reverse proxies.
|
||||
* The significant difference is that unlike MojangResolver from the CraftAPI implementation, which sends the
|
||||
* "ip" parameter when the hostIp parameter is an IPv4 address, but skips it for IPv6, this implementation leaves out
|
||||
* the "ip" parameter also for IPv4, effectively enabling transparent proxies to work.
|
||||
*
|
||||
* The significant difference is that unlike MojangResolver from the CraftAPI implementation, which sends the "ip" parameter
|
||||
* when the hostIp parameter is an IPv4 address, but skips it for IPv6, this implementation leaves out the "ip" parameter
|
||||
* also for IPv4, effectively enabling transparent proxies to work.
|
||||
* @author games647, Enginecrafter77
|
||||
*/
|
||||
public class ProxyAgnosticMojangResolver extends MojangResolver {
|
||||
/**
|
||||
* A formatting string containing an URL used to call the {@code hasJoined} method on mojang session servers.
|
||||
*
|
||||
* Formatting parameters:
|
||||
* 1. The username of the player in question
|
||||
* 2. The serverId of this server
|
||||
*/
|
||||
public static final String MOJANG_SESSIONSERVER_HASJOINED_CALL_URLFMT = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s";
|
||||
|
||||
private static final String HOST = "sessionserver.mojang.com";
|
||||
@Override
|
||||
public Optional<Verification> hasJoined(String username, String serverHash, InetAddress hostIp) throws IOException
|
||||
{
|
||||
String url = String.format(MOJANG_SESSIONSERVER_HASJOINED_CALL_URLFMT, username, serverHash);
|
||||
|
||||
/**
|
||||
* A formatting string containing a URL used to call the {@code hasJoined} method on mojang session servers.
|
||||
* <p>
|
||||
* Formatting parameters:
|
||||
* 1. The username of the player in question
|
||||
* 2. The serverId of this server
|
||||
*/
|
||||
public static final String ENDPOINT = "https://" + HOST + "/session/minecraft/hasJoined?username=%s&serverId=%s";
|
||||
HttpURLConnection conn = this.getConnection(url);
|
||||
int responseCode = conn.getResponseCode();
|
||||
|
||||
@Override
|
||||
public Optional<Verification> hasJoined(String username, String serverHash, InetAddress hostIp)
|
||||
throws IOException {
|
||||
String url = String.format(ENDPOINT, username, serverHash);
|
||||
Verification verification = null;
|
||||
|
||||
HttpURLConnection conn = this.getConnection(url);
|
||||
int responseCode = conn.getResponseCode();
|
||||
// Mojang session servers send HTTP 204 (NO CONTENT) when the authentication seems invalid
|
||||
// If that's not our case, the authentication is valid, and so we can parse the response.
|
||||
if(responseCode != HttpURLConnection.HTTP_NO_CONTENT)
|
||||
verification = this.parseRequest(conn, this::parseVerification);
|
||||
|
||||
Verification verification = null;
|
||||
return Optional.ofNullable(verification);
|
||||
}
|
||||
|
||||
// Mojang session servers send HTTP 204 (NO CONTENT) when the authentication seems invalid
|
||||
// If that's not our case, the authentication is valid, and so we can parse the response.
|
||||
if (responseCode != HttpURLConnection.HTTP_NO_CONTENT) {
|
||||
verification = this.parseRequest(conn, this::parseVerification);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(verification);
|
||||
}
|
||||
|
||||
// Functional implementation of InputStreamAction, used in hasJoined method in parseRequest call
|
||||
protected Verification parseVerification(InputStream input) throws IOException {
|
||||
return this.readJson(input, Verification.class);
|
||||
}
|
||||
// Functional implementation of InputStreamAction, used in hasJoined method in parseRequest call
|
||||
protected Verification parseVerification(InputStream input) throws IOException
|
||||
{
|
||||
return this.readJson(input, Verification.class);
|
||||
}
|
||||
}
|
||||
@@ -23,22 +23,3 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.Instant;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Value(staticConstructor = "of")
|
||||
public class ClientPublicKey {
|
||||
Instant expiry;
|
||||
PublicKey key;
|
||||
byte[] signature;
|
||||
|
||||
public boolean isExpired(Instant verifyTimestamp) {
|
||||
return !verifyTimestamp.isBefore(expiry);
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,8 @@ package com.github.games647.fastlogin.core.shared;
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.github.games647.fastlogin.core.antibot.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.antibot.TickingRateLimiter;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.TickingRateLimiter;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
@@ -90,7 +88,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
|
||||
private Configuration config;
|
||||
private SQLStorage storage;
|
||||
private AntiBotService antiBot;
|
||||
private RateLimiter rateLimiter;
|
||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
private AuthPlugin<P> authPlugin;
|
||||
|
||||
@@ -122,10 +120,9 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
}
|
||||
|
||||
// Initialize the resolver based on the config parameter
|
||||
this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false)
|
||||
? new ProxyAgnosticMojangResolver() : new MojangResolver();
|
||||
this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false) ? new ProxyAgnosticMojangResolver() : new MojangResolver();
|
||||
|
||||
antiBot = createAntiBotService(config.getSection("anti-bot"));
|
||||
rateLimiter = createRateLimiter(config.getSection("anti-bot"));
|
||||
Set<Proxy> proxies = config.getStringList("proxies")
|
||||
.stream()
|
||||
.map(HostAndPort::fromString)
|
||||
@@ -147,34 +144,20 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
resolver.setOutgoingAddresses(addresses);
|
||||
}
|
||||
|
||||
private AntiBotService createAntiBotService(Configuration botSection) {
|
||||
RateLimiter rateLimiter;
|
||||
if (botSection.getBoolean("enabled", true)) {
|
||||
int maxCon = botSection.getInt("connections", 200);
|
||||
long expireTime = botSection.getLong("expire", 5) * 60 * 1_000L;
|
||||
if (expireTime > MAX_EXPIRE_RATE) {
|
||||
expireTime = MAX_EXPIRE_RATE;
|
||||
}
|
||||
|
||||
rateLimiter = new TickingRateLimiter(Ticker.systemTicker(), maxCon, expireTime);
|
||||
} else {
|
||||
private RateLimiter createRateLimiter(Configuration botSection) {
|
||||
boolean enabled = botSection.getBoolean("enabled", true);
|
||||
if (!enabled) {
|
||||
// no-op rate limiter
|
||||
rateLimiter = () -> true;
|
||||
return () -> true;
|
||||
}
|
||||
|
||||
Action action = Action.Ignore;
|
||||
switch (botSection.getString("action", "ignore")) {
|
||||
case "ignore":
|
||||
action = Action.Ignore;
|
||||
break;
|
||||
case "block":
|
||||
action = Action.Block;
|
||||
break;
|
||||
default:
|
||||
plugin.getLog().warn("Invalid anti bot action - defaulting to ignore");
|
||||
int maxCon = botSection.getInt("anti-bot.connections", 200);
|
||||
long expireTime = botSection.getLong("anti-bot.expire", 5) * 60 * 1_000L;
|
||||
if (expireTime > MAX_EXPIRE_RATE) {
|
||||
expireTime = MAX_EXPIRE_RATE;
|
||||
}
|
||||
|
||||
return new AntiBotService(plugin.getLog(), rateLimiter, action);
|
||||
return new TickingRateLimiter(Ticker.systemTicker(), maxCon, expireTime);
|
||||
}
|
||||
|
||||
private Configuration loadFile(String fileName) throws IOException {
|
||||
@@ -192,7 +175,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
config = configProvider.load(reader, defaults);
|
||||
}
|
||||
|
||||
// explicitly add keys here, because Configuration.getKeys doesn't return the keys from the default config
|
||||
// explicitly add keys here, because Configuration.getKeys doesn't return the keys from the default configuration
|
||||
for (String key : defaults.getKeys()) {
|
||||
config.set(key, config.get(key));
|
||||
}
|
||||
@@ -245,13 +228,9 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
boolean useSSL = config.get("useSSL", false);
|
||||
|
||||
if (useSSL) {
|
||||
boolean publicKeyRetrieval = config.getBoolean("allowPublicKeyRetrieval", false);
|
||||
String rsaPublicKeyFile = config.getString("ServerRSAPublicKeyFile");
|
||||
String sslMode = config.getString("sslMode", "Required");
|
||||
|
||||
databaseConfig.addDataSourceProperty("allowPublicKeyRetrieval", publicKeyRetrieval);
|
||||
databaseConfig.addDataSourceProperty("serverRSAPublicKeyFile", rsaPublicKeyFile);
|
||||
databaseConfig.addDataSourceProperty("sslMode", sslMode);
|
||||
databaseConfig.addDataSourceProperty("allowPublicKeyRetrieval", config.getBoolean("allowPublicKeyRetrieval", false));
|
||||
databaseConfig.addDataSourceProperty("serverRSAPublicKeyFile", config.getString("ServerRSAPublicKeyFile"));
|
||||
databaseConfig.addDataSourceProperty("sslMode", config.getString("sslMode", "Required"));
|
||||
}
|
||||
|
||||
databaseConfig.setUsername(config.get("username", ""));
|
||||
@@ -275,8 +254,8 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
} catch (ClassNotFoundException notFoundEx) {
|
||||
Logger log = plugin.getLog();
|
||||
log.warn("This driver {} is not supported on this platform", className);
|
||||
log.warn("Please choose either MySQL (Spigot, BungeeCord), SQLite (Spigot, Sponge) or "
|
||||
+ "MariaDB (Sponge, Velocity)", notFoundEx);
|
||||
log.warn("Please choose either MySQL (Spigot, BungeeCord), SQLite (Spigot, Sponge) or " +
|
||||
"MariaDB (Sponge, Velocity)", notFoundEx);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -306,8 +285,8 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public AntiBotService getAntiBot() {
|
||||
return antiBot;
|
||||
public RateLimiter getRateLimiter() {
|
||||
return rateLimiter;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.SQLStorage;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.SQLStorage;
|
||||
|
||||
public abstract class ForceLoginManagement<P extends C, C, L extends LoginSession, T extends PlatformPlugin<C>>
|
||||
implements Runnable {
|
||||
|
||||
@@ -93,7 +93,7 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
|
||||
premiumUUID = core.getResolver().findProfile(username);
|
||||
}
|
||||
|
||||
if (!premiumUUID.isPresent()
|
||||
if (premiumUUID.isEmpty()
|
||||
|| (!checkNameChange(source, username, premiumUUID.get())
|
||||
&& !checkPremiumName(source, username, profile))) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
@@ -106,9 +106,9 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
|
||||
}
|
||||
}
|
||||
} catch (RateLimitException rateLimitEx) {
|
||||
core.getPlugin().getLog().error("Mojang's rate limit reached for {}. The public IPv4 address of this"
|
||||
+ " server issued more than 600 Name -> UUID requests within 10 minutes. After those 10"
|
||||
+ " minutes we can make requests again.", username);
|
||||
core.getPlugin().getLog().error("Mojang's rate limit reached for {}. The public IPv4 address of this" +
|
||||
" server issued more than 600 Name -> UUID requests within 10 minutes. After those 10" +
|
||||
" minutes we can make requests again.", username);
|
||||
} catch (Exception ex) {
|
||||
core.getPlugin().getLog().error("Failed to check premium state for {}", username, ex);
|
||||
core.getPlugin().getLog().error("Failed to check premium state of {}", username, ex);
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class LoginSession {
|
||||
@@ -52,7 +52,7 @@ public abstract class LoginSession {
|
||||
return requestUsername;
|
||||
}
|
||||
|
||||
public synchronized String getUsername() {
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@@ -90,13 +90,13 @@ public abstract class LoginSession {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", LoginSession.class.getSimpleName() + "[", "]")
|
||||
.add("profile=" + profile)
|
||||
.add("requestUsername='" + requestUsername + "'")
|
||||
.add("username='" + username + "'")
|
||||
.add("uuid=" + uuid)
|
||||
.add("registered=" + registered)
|
||||
public synchronized String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("profile", profile)
|
||||
.add("requestUsername", requestUsername)
|
||||
.add("username", username)
|
||||
.add("uuid", uuid)
|
||||
.add("registered", registered)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public interface PlatformPlugin<C> {
|
||||
default ThreadFactory getThreadFactory() {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(getName() + " Pool Thread #%1$d")
|
||||
// Hikari create daemons by default. We could use daemon threads for our own scheduler too
|
||||
// Hikari create daemons by default. We could daemon threads for our own scheduler too
|
||||
// because we safely shut down
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.zaxxer.hikari.HikariConfig;
|
||||
public class MySQLStorage extends SQLStorage {
|
||||
|
||||
public MySQLStorage(FastLoginCore<?, ?, ?> core, String driver, String host, int port, String database,
|
||||
HikariConfig config, boolean useSSL) {
|
||||
HikariConfig config,boolean useSSL) {
|
||||
super(core,
|
||||
buildJDBCUrl(driver, host, port, database),
|
||||
setParams(config, useSSL));
|
||||
|
||||
@@ -20,9 +20,6 @@ anti-bot:
|
||||
connections: 600
|
||||
# Amount of minutes after the first connection got inserted will expire and made available
|
||||
expire: 10
|
||||
# Action - Which action should be performed when the bucket is full (too many connections)
|
||||
# Allowed values are: 'ignore' (FastLogin drops handling the player) or 'block' (block this incoming connection)
|
||||
action: 'ignore'
|
||||
|
||||
# Request a premium login without forcing the player to type a command
|
||||
#
|
||||
@@ -272,15 +269,6 @@ autoRegisterFloodgate: false
|
||||
# Enabling this might lead to people gaining unauthorized access to other's accounts!
|
||||
floodgatePrefixWorkaround: false
|
||||
|
||||
# This option resembles the vanilla configuration option 'enforce-secure-profile' in the 'server.properties' file.
|
||||
# It verifies if the incoming cryptographic key in the login request from the player is signed by Mojang. This key
|
||||
# is necessary for servers where you or other in-game players want to verify that a chat message sent and signed by
|
||||
# this player is not modified by any third-party. Modifications by your server would also invalidate the message.
|
||||
#
|
||||
# This feature is only relevant if you use the plugin in ProtocolLib mode and use 1.19+.
|
||||
# This also the case if you don't have any proxies in use.
|
||||
verifyClientKeys: false
|
||||
|
||||
# Database configuration
|
||||
# Recommended is the use of MariaDB (a better version of MySQL)
|
||||
|
||||
|
||||
@@ -48,9 +48,6 @@ not-premium-other: '&4Player is not in the premium list'
|
||||
# Admin wanted to change the premium of a user that isn't known to the plugin
|
||||
player-unknown: '&4Player not in the database'
|
||||
|
||||
# Player kicked from anti bot feature
|
||||
kick-antibot: '&4Please wait a moment!'
|
||||
|
||||
# ========= Bukkit/Spigot ================
|
||||
|
||||
# The user skipped the authentication, because it was a premium player
|
||||
@@ -80,7 +77,7 @@ error-kick: '&4Error occurred'
|
||||
|
||||
# The server sends a verify-token within the premium authentication request. If this doesn't match on response,
|
||||
# it could be another client sending malicious packets
|
||||
invalid-verify-token: '&4Invalid nonce token. Please verify you are using the correct server address'
|
||||
invalid-verify-token: '&4Invalid token'
|
||||
|
||||
# The client sent no request join server request to the mojang servers which would proof that it's owner of that
|
||||
# account. Only modified clients would do this.
|
||||
@@ -96,9 +93,6 @@ not-started: '&cServer is not fully started yet. Please retry'
|
||||
premium-warning: '&c&lWARNING: &6This command should&l only&6 be invoked if you are the owner of this paid Minecraft account
|
||||
Type &a/premium&6 again to confirm'
|
||||
|
||||
# Invalid client public key that will be used in the future to send authenticated chat messages from clients
|
||||
invalid-public-key: '&cInvalid client public key. Please try to restart your game.'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
||||
|
||||
@@ -25,80 +25,132 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.fastlogin.core.antibot.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.antibot.TickingRateLimiter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
class TickingRateLimiterTest {
|
||||
public class TickingRateLimiterTest {
|
||||
|
||||
private static final long THRESHOLD_MILLI = 10;
|
||||
|
||||
/**
|
||||
* Always expired
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = {5_000_000L, -5_000_000L})
|
||||
void allowExpire(long initial) {
|
||||
@Test
|
||||
public void allowExpire() throws InterruptedException {
|
||||
int size = 3;
|
||||
|
||||
FakeTicker ticker = new FakeTicker(initial);
|
||||
FakeTicker ticker = new FakeTicker(5_000_000L);
|
||||
|
||||
// run twice the size to fill it first and then test it
|
||||
RateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0);
|
||||
TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0);
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue(rateLimiter.tryAcquire(), "Filling up");
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
ticker.add(Duration.ofSeconds(1));
|
||||
assertTrue(rateLimiter.tryAcquire(), "Should be expired");
|
||||
assertTrue("Should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowExpireNegative() throws InterruptedException {
|
||||
int size = 3;
|
||||
|
||||
FakeTicker ticker = new FakeTicker(-5_000_000L);
|
||||
|
||||
// run twice the size to fill it first and then test it
|
||||
TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0);
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
ticker.add(Duration.ofSeconds(1));
|
||||
assertTrue("Should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too many requests
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = {5_000_000L, -5_000_000L})
|
||||
void shouldBlock(long initial) {
|
||||
@Test
|
||||
public void shouldBlock() {
|
||||
int size = 3;
|
||||
|
||||
FakeTicker ticker = new FakeTicker(initial);
|
||||
FakeTicker ticker = new FakeTicker(5_000_000L);
|
||||
|
||||
// fill the size
|
||||
RateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30));
|
||||
TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30));
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue(rateLimiter.tryAcquire(), "Filling up");
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
assertFalse(rateLimiter.tryAcquire(), "Should be full and no entry should be expired");
|
||||
assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Too many requests
|
||||
*/
|
||||
@Test
|
||||
public void shouldBlockNegative() {
|
||||
int size = 3;
|
||||
|
||||
FakeTicker ticker = new FakeTicker(-5_000_000L);
|
||||
|
||||
// fill the size
|
||||
TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30));
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocked attempts shouldn't replace existing ones.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = {5_000_000L, -5_000_000L})
|
||||
void blockedNotAdded(long initial) {
|
||||
FakeTicker ticker = new FakeTicker(initial);
|
||||
@Test
|
||||
public void blockedNotAdded() throws InterruptedException {
|
||||
FakeTicker ticker = new FakeTicker(5_000_000L);
|
||||
|
||||
// fill the size - 100ms should be reasonable high
|
||||
RateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100);
|
||||
assertTrue(rateLimiter.tryAcquire(), "Filling up");
|
||||
TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100);
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
|
||||
ticker.add(Duration.ofMillis(50));
|
||||
|
||||
// still is full - should fail
|
||||
assertFalse(rateLimiter.tryAcquire(), "Expired too early");
|
||||
assertFalse("Expired too early", rateLimiter.tryAcquire());
|
||||
|
||||
// wait the remaining time and add a threshold, because
|
||||
ticker.add(Duration.ofMillis(50));
|
||||
assertTrue(rateLimiter.tryAcquire(), "Request not released");
|
||||
assertTrue("Request not released", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocked attempts shouldn't replace existing ones.
|
||||
*/
|
||||
@Test
|
||||
public void blockedNotAddedNegative() throws InterruptedException {
|
||||
FakeTicker ticker = new FakeTicker(-5_000_000L);
|
||||
|
||||
// fill the size - 100ms should be reasonable high
|
||||
TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100);
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
|
||||
ticker.add(Duration.ofMillis(50));
|
||||
|
||||
// still is full - should fail
|
||||
assertFalse("Expired too early", rateLimiter.tryAcquire());
|
||||
|
||||
// wait the remaining time and add a threshold, because
|
||||
ticker.add(Duration.ofMillis(50));
|
||||
assertTrue("Request not released", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
|
||||
74
pom.xml
74
pom.xml
@@ -35,7 +35,7 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
@@ -48,14 +48,11 @@
|
||||
<!-- Set default for non-git clones -->
|
||||
<git.commit.id>Unknown</git.commit.id>
|
||||
|
||||
<!-- Verify Java 8 compatibility while compiling with a newer toolchain
|
||||
(i.e. check for unavailable methods) -->
|
||||
<java.version>8</java.version>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven.compiler.release>${java.version}</maven.compiler.release>
|
||||
|
||||
<floodgate.version>2.2.0-SNAPSHOT</floodgate.version>
|
||||
<floodgate.version>2.0-SNAPSHOT</floodgate.version>
|
||||
<geyser.version>2.0.0-SNAPSHOT</geyser.version>
|
||||
</properties>
|
||||
|
||||
@@ -128,54 +125,11 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
<failsOnError>true</failsOnError>
|
||||
<linkXRef>false</linkXRef>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.3.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>validate</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Require newer versions for Junit5 support -->
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
<resource>
|
||||
<!-- Certificate should not be filtered, as it would make it invalid -->
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>yggdrasil_session_pubkey.der</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<excludes>
|
||||
<exclude>yggdrasil_session_pubkey.der</exclude>
|
||||
</excludes>
|
||||
<!--Replace variables-->
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
@@ -183,26 +137,10 @@
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- Use lombok to use some newer Java syntax features in Java 8 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.24</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Require inline to support static mocks -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<version>4.6.1</version>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -59,11 +59,10 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<relocations>
|
||||
@@ -97,12 +96,6 @@
|
||||
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.mariadb.jdbc:*</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
@@ -120,7 +113,7 @@
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>velocity</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
<url>https://nexus.velocitypowered.com/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
@@ -136,7 +129,7 @@
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -144,7 +137,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>3.0.6</version>
|
||||
<version>3.0.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -46,7 +46,6 @@ import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
|
||||
@@ -74,7 +73,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
private final Path dataDirectory;
|
||||
private final Logger logger;
|
||||
private final ConcurrentMap<InetSocketAddress, VelocityLoginSession> session = new MapMaker().weakKeys().makeMap();
|
||||
private static final String PROXY_ID_FILE = "proxyId.txt";
|
||||
private static final String PROXY_ID_fILE = "proxyId.txt";
|
||||
|
||||
private FastLoginCore<Player, CommandSource, FastLoginVelocity> core;
|
||||
private AsyncScheduler scheduler;
|
||||
@@ -97,12 +96,10 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
return;
|
||||
}
|
||||
|
||||
server.getEventManager().register(this, new ConnectListener(this, core.getAntiBot()));
|
||||
server.getEventManager().register(this, new ConnectListener(this, core.getRateLimiter()));
|
||||
server.getEventManager().register(this, new PluginMessageListener(this));
|
||||
|
||||
ChannelRegistrar channelRegistry = server.getChannelRegistrar();
|
||||
channelRegistry.register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
|
||||
channelRegistry.register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL));
|
||||
server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
|
||||
server.getChannelRegistrar().register(MinecraftChannelIdentifier.create(getName(), SuccessMessage.SUCCESS_CHANNEL));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -170,7 +167,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
}
|
||||
|
||||
private void loadOrGenerateProxyId() {
|
||||
Path idFile = dataDirectory.resolve(PROXY_ID_FILE);
|
||||
Path idFile = dataDirectory.resolve(PROXY_ID_fILE);
|
||||
boolean shouldGenerate = false;
|
||||
|
||||
if (Files.exists(idFile)) {
|
||||
@@ -185,8 +182,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
logger.error("Unable to load proxy id from '{}'", idFile.toAbsolutePath());
|
||||
logger.error("Detailed exception:", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Path filePath = idFile.toAbsolutePath();
|
||||
logger.error("'{}' contains an invalid uuid! FastLogin will not work without a valid id.", filePath);
|
||||
logger.error("'{}' contains an invalid uuid! FastLogin will not work without a valid id.", idFile.toAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
shouldGenerate = true;
|
||||
|
||||
@@ -58,10 +58,10 @@ public class VelocityLoginSession extends LoginSession {
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{'
|
||||
+ "alreadySaved=" + alreadySaved
|
||||
+ ", alreadyLogged=" + alreadyLogged
|
||||
+ ", registered=" + registered
|
||||
+ "} " + super.toString();
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"alreadySaved=" + alreadySaved +
|
||||
", alreadyLogged=" + alreadyLogged +
|
||||
", registered=" + registered +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import com.velocitypowered.api.event.ResultedEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
public class VelocityFastLoginAutoLoginEvent
|
||||
implements FastLoginAutoLoginEvent, ResultedEvent<ResultedEvent.GenericResult> {
|
||||
implements FastLoginAutoLoginEvent, ResultedEvent<ResultedEvent.GenericResult> {
|
||||
|
||||
private final LoginSession session;
|
||||
private final StoredProfile profile;
|
||||
@@ -67,11 +67,11 @@ public class VelocityFastLoginAutoLoginEvent
|
||||
|
||||
@Override
|
||||
public GenericResult getResult() {
|
||||
return cancelled ? GenericResult.denied() : GenericResult.allowed();
|
||||
return cancelled ? GenericResult.denied(): GenericResult.allowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResult(GenericResult result) {
|
||||
cancelled = Objects.requireNonNull(result) != GenericResult.allowed();
|
||||
cancelled = Objects.requireNonNull(result) != GenericResult.allowed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
package com.github.games647.fastlogin.velocity.listener;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.velocity.FastLoginVelocity;
|
||||
import com.github.games647.fastlogin.velocity.VelocityLoginSession;
|
||||
@@ -38,33 +37,26 @@ import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.GameProfile.Property;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
public class ConnectListener {
|
||||
|
||||
private final FastLoginVelocity plugin;
|
||||
private final AntiBotService antiBotService;
|
||||
private final RateLimiter rateLimiter;
|
||||
|
||||
public ConnectListener(FastLoginVelocity plugin, AntiBotService antiBotService) {
|
||||
public ConnectListener(FastLoginVelocity plugin, RateLimiter rateLimiter) {
|
||||
this.plugin = plugin;
|
||||
this.antiBotService = antiBotService;
|
||||
this.rateLimiter = rateLimiter;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -74,30 +66,16 @@ public class ConnectListener {
|
||||
}
|
||||
|
||||
InboundConnection connection = preLoginEvent.getConnection();
|
||||
String username = preLoginEvent.getUsername();
|
||||
InetSocketAddress address = connection.getRemoteAddress();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, address);
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
// just ignore
|
||||
return;
|
||||
case Block:
|
||||
String message = plugin.getCore().getMessage("kick-antibot");
|
||||
TextComponent messageParsed = LegacyComponentSerializer.legacyAmpersand().deserialize(message);
|
||||
|
||||
PreLoginComponentResult reason = PreLoginComponentResult.denied(messageParsed);
|
||||
preLoginEvent.setResult(reason);
|
||||
break;
|
||||
case Continue:
|
||||
default:
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(
|
||||
plugin, connection, username, continuation, preLoginEvent
|
||||
);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
break;
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection);
|
||||
return;
|
||||
}
|
||||
|
||||
String username = preLoginEvent.getUsername();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getRemoteAddress());
|
||||
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, connection, username, continuation, preLoginEvent);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -120,18 +98,16 @@ public class ConnectListener {
|
||||
}
|
||||
|
||||
if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
|
||||
List<Property> newProp = removeSkin(event.getGameProfile().getProperties());
|
||||
event.setGameProfile(event.getGameProfile().withProperties(newProp));
|
||||
event.setGameProfile(event.getGameProfile().withProperties(removeSkin(event.getGameProfile().getProperties())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<GameProfile.Property> removeSkin(Collection<Property> oldProperties) {
|
||||
List<GameProfile.Property> newProperties = new ArrayList<>(oldProperties.size());
|
||||
private List<GameProfile.Property> removeSkin(List<GameProfile.Property> oldProperties) {
|
||||
List<GameProfile.Property> newProperties = new ArrayList<>(oldProperties.size() - 1);
|
||||
for (GameProfile.Property property : oldProperties) {
|
||||
if (!"textures".equals(property.getName())) {
|
||||
if (!"textures".equals(property.getName()))
|
||||
newProperties.add(property);
|
||||
}
|
||||
}
|
||||
|
||||
return newProperties;
|
||||
|
||||
@@ -55,9 +55,8 @@ public class PluginMessageListener {
|
||||
public PluginMessageListener(FastLoginVelocity plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
String prefix = plugin.getName();
|
||||
this.successChannel = MinecraftChannelIdentifier.create(prefix, SuccessMessage.SUCCESS_CHANNEL).getId();
|
||||
this.changeChannel = MinecraftChannelIdentifier.create(prefix, ChangePremiumMessage.CHANGE_CHANNEL).getId();
|
||||
this.successChannel = MinecraftChannelIdentifier.create(plugin.getName(), SuccessMessage.SUCCESS_CHANNEL).getId();
|
||||
this.changeChannel = MinecraftChannelIdentifier.create(plugin.getName(), ChangePremiumMessage.CHANGE_CHANNEL).getId();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -96,9 +95,8 @@ public class PluginMessageListener {
|
||||
String playerName = changeMessage.getPlayerName();
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
if (changeMessage.shouldEnable()) {
|
||||
Boolean premiumWarning = plugin.getCore().getConfig().get("premium-warning", true);
|
||||
if (playerName.equals(forPlayer.getUsername()) && premiumWarning
|
||||
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
if (playerName.equals(forPlayer.getUsername()) && plugin.getCore().getConfig().get("premium-warning", true)
|
||||
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = core.getMessage("premium-warning");
|
||||
forPlayer.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
|
||||
core.getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
@@ -116,7 +114,7 @@ public class PluginMessageListener {
|
||||
}
|
||||
|
||||
private void onSuccessMessage(Player forPlayer) {
|
||||
if (forPlayer.isOnlineMode()) {
|
||||
if (forPlayer.isOnlineMode()){
|
||||
//bukkit module successfully received and force logged in the user
|
||||
//update only on success to prevent corrupt data
|
||||
VelocityLoginSession loginSession = plugin.getSession().get(forPlayer.getRemoteAddress());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user