mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-23 15:18:09 +01:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87ca00d75d | ||
|
|
62ffb1a904 | ||
|
|
5075a71843 | ||
|
|
da266c7e91 | ||
|
|
acab4766b1 | ||
|
|
bef90d11cd | ||
|
|
a02acd2d63 | ||
|
|
ca42a7c19e | ||
|
|
b533197f05 | ||
|
|
c94711f315 | ||
|
|
ee7af80bf0 | ||
|
|
17c2099bf1 | ||
|
|
31d6b67381 | ||
|
|
4b423c9ccb | ||
|
|
4292e9aaa0 | ||
|
|
07d0aededa | ||
|
|
218bc50c96 | ||
|
|
a3b2e33aad | ||
|
|
76f5ba7ed1 | ||
|
|
2cd50d23ad | ||
|
|
9f5f61f1c2 | ||
|
|
3e9c8e3a7e | ||
|
|
8e5da01be0 | ||
|
|
5022c9aa7b | ||
|
|
ad1ab22586 | ||
|
|
99ef5ce726 | ||
|
|
9b7634a9f3 | ||
|
|
115fc2e7ba | ||
|
|
b660951e1e | ||
|
|
e495f70ccd | ||
|
|
b35d67b5c0 | ||
|
|
58ac73a5a9 | ||
|
|
ebe768f7a2 | ||
|
|
d20db79f46 | ||
|
|
c28d889c1b | ||
|
|
ad60397851 | ||
|
|
88fdeff3f1 | ||
|
|
558ee1c92c | ||
|
|
3e84ebd787 | ||
|
|
36d7564c3a | ||
|
|
596caa0573 | ||
|
|
fe4331298f | ||
|
|
a67d84ef3f | ||
|
|
71362dfd7d | ||
|
|
fcd98fce43 | ||
|
|
6c1c4e7286 | ||
|
|
164fb735d6 | ||
|
|
fa1b0970a5 | ||
|
|
974bf498fc | ||
|
|
27c04ff08f | ||
|
|
fb357424e6 | ||
|
|
c73bb70256 | ||
|
|
dc395cdc3f | ||
|
|
f7626ab969 | ||
|
|
5f9802d589 | ||
|
|
642c1621ad | ||
|
|
eb965d5a48 | ||
|
|
457bc9cf47 | ||
|
|
2ab3c6b77c | ||
|
|
f27bad02d3 | ||
|
|
9334296beb | ||
|
|
fd9940e6f0 | ||
|
|
0745957e79 | ||
|
|
bb2e60f6e1 | ||
|
|
d15861b8e5 | ||
|
|
b84b340a77 | ||
|
|
c50249edea | ||
|
|
757ddb905a | ||
|
|
9914b7f358 | ||
|
|
bba4eb4eec | ||
|
|
2b16f3341f | ||
|
|
167ce66057 | ||
|
|
8d1021e44c | ||
|
|
a811a741f5 | ||
|
|
a6348766b3 | ||
|
|
22dcc50950 | ||
|
|
bd3494eed0 | ||
|
|
1aba9a0f3b | ||
|
|
6faf00e1bf | ||
|
|
0d89614f3c | ||
|
|
b009658eea | ||
|
|
2881689f09 | ||
|
|
6d1a97fd32 | ||
|
|
b74faa2fd5 | ||
|
|
4800a88886 | ||
|
|
92c9ab5b76 | ||
|
|
d90e3fdb44 | ||
|
|
8abbb8f07c | ||
|
|
f04a44b1d2 |
@@ -8,8 +8,5 @@ language: java
|
||||
|
||||
script: mvn compile test
|
||||
|
||||
# We run on 7+
|
||||
jdk:
|
||||
- openjdk7
|
||||
- oraclejdk7
|
||||
- oraclejdk8
|
||||
# We run on 8
|
||||
jdk: [oraclejdk8]
|
||||
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,7 +1,73 @@
|
||||
######1.9
|
||||
|
||||
* Added second attempt login -> cracked login
|
||||
* Added cracked whitelist (switch-mode -> switching to online-mode from offlinemode)
|
||||
* Added configuration to disable auto logins for 2Factor authentication
|
||||
* Added missing add-premium-other message
|
||||
* Upgrade to Java 8 -> Minimize file size
|
||||
* Refactored/Cleaned up a lot of code
|
||||
* [API] Deprecated platform specific authplugin. Please use AuthPlugin< platform specific player type >
|
||||
* [API] Deprecated bukkit's password generator. Please use PasswordGenerator< platform specific player type >
|
||||
* Fix ProtocolSupport autoRegister
|
||||
* Fix update username in FastLogin database after nameChange
|
||||
* Fix logging exceptions on encryption enabling
|
||||
* Fix compatibility with older ProtocolLib versions (for 1.7) because of the missing getMethodAcccessorOrNull method
|
||||
* Fix correct cracked permission for bukkit
|
||||
* A try to fix SQLite timestamp parsing
|
||||
* Drop support for LoginSecurity 1.X since 2.X seems to be stable
|
||||
* Remove the nasty UltraAuth fakeplayer workaround by using a new api method. You should UltraAuth if you have it
|
||||
|
||||
######1.8
|
||||
|
||||
* Added autoIn importer
|
||||
* Added BFA importer
|
||||
* Added ElDziAuth importer
|
||||
* Fix third-party not premium player detection
|
||||
* Fix ProtocolSupport BungeeCord
|
||||
* Fix duplicate logins for BungeeAuth users
|
||||
|
||||
######1.7.1
|
||||
|
||||
* Fix BungeeCord autoRegister (Fixes #46)
|
||||
* Fix protocollsupport autoregister
|
||||
|
||||
######1.7
|
||||
|
||||
* Added support for making requests to Mojang from different IPv4 addresses
|
||||
* Added us.mcapi.com as third-party APIs to workaround rate-limits
|
||||
* Fixed NPE in BungeeCord on cracked session
|
||||
* Fixed skin applies if premium uuid is deactivated
|
||||
* 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 an premium uuid
|
||||
* Fix setting skin on Cauldron
|
||||
* Fix saving on name change
|
||||
|
||||
######1.6.2
|
||||
|
||||
* Fixed support for new LoginSecurity version
|
||||
|
||||
######1.6.1
|
||||
|
||||
* Fix message typo in BungeeCord which created a NPE if premium-warning is activated
|
||||
|
||||
######1.6
|
||||
|
||||
* Add a warning message if the user tries to invoke the premium command
|
||||
* Added missing translation if the server isn't fully started
|
||||
* Removed ProtocolLib as required dependency. You can use ProtocolSupport or BungeeCord as alternative
|
||||
* Reduce the number of worker threads from 5 to 3 in ProtocolLib
|
||||
* Process packets in ProtocolLib async/non-blocking -> better performance
|
||||
* Fixed missing translation in commands
|
||||
* Fixed cracked command not working on BungeeCord
|
||||
* Fix error if forward skins is disabled
|
||||
|
||||
######1.5.2
|
||||
|
||||
* Fixed BungeeCord force logins if there is a lobby server
|
||||
* Removed cache expire in BungeeCord
|
||||
* Applies skin earlier to make it visible for other plugins listening on login events
|
||||
|
||||
######1.5.1
|
||||
|
||||
|
||||
@@ -22,6 +22,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 non blocking operations
|
||||
* Locale messages
|
||||
* Import the database from similar plugins
|
||||
* Free
|
||||
* Open source
|
||||
|
||||
@@ -30,10 +31,14 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
|
||||
###Commands:
|
||||
* /premium [player] Label the invoker or the argument as paid account
|
||||
* /cracked [player] Label the invoker or the argument as cracked account
|
||||
* /importdb <autoIn/bpa/eldzi> <mysql/sqlite> [host:port] [database] [username] [password] - Imports the database from another plugin
|
||||
|
||||
###Permissions:
|
||||
* fastlogin.bukkit.command.premium
|
||||
* fastlogin.bukkit.command.cracked
|
||||
* fastlogin.command.premium.other
|
||||
* fastlogin.command.cracked.other
|
||||
* fastlogin.command.import
|
||||
|
||||
###Requirements:
|
||||
* Plugin: [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/) or [ProtocolSupport](http://www.spigotmc.org/resources/protocolsupport.7201/)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
bukkit/lib/UltraAuth v2.1.2.jar
Normal file
BIN
bukkit/lib/UltraAuth v2.1.2.jar
Normal file
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<!--LoginSecurity-->
|
||||
<repository>
|
||||
<id>lenis0012-repo</id>
|
||||
<url>http://ci.lenis0012.com/plugin/repository/everything/</url>
|
||||
</repository>
|
||||
|
||||
<!--ProtocolLib-->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
@@ -40,11 +46,6 @@
|
||||
<url>http://repo.luricos.de/bukkit-plugins/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>logit-only-repo</id>
|
||||
<url>http://ci.ac3-servers.eu/job/LogIt-Classic/2/maven-repository/repository/</url>
|
||||
</repository>
|
||||
|
||||
<!--Github automatic maven builds-->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
@@ -64,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.9-R0.1-SNAPSHOT</version>
|
||||
<version>1.10-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -72,17 +73,15 @@
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<optional>true</optional>
|
||||
<version>3.6.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>protcolsupport</groupId>
|
||||
<artifactId>ProtocolSupport</artifactId>
|
||||
<version>Build-337</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/ProtocolSupport b337.jar</systemPath>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ProtocolSupport</groupId>
|
||||
<artifactId>ProtocolSupport</artifactId>
|
||||
<!--4.25.dev-->
|
||||
<version>5554413b51</version>
|
||||
</dependency>
|
||||
|
||||
<!--Login Plugins-->
|
||||
<dependency>
|
||||
@@ -99,10 +98,10 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.lenis0012</groupId>
|
||||
<artifactId>LoginSecurity-2</artifactId>
|
||||
<!--Old version 2.0-->
|
||||
<version>-9c09e73b7f-1</version>
|
||||
<groupId>com.lenis0012.bukkit</groupId>
|
||||
<artifactId>loginsecurity</artifactId>
|
||||
<version>2.1.3-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
@@ -111,22 +110,25 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.lucaseasedup.logit</groupId>
|
||||
<artifactId>LogIt</artifactId>
|
||||
<version>SNAPSHOT</version>
|
||||
<dependency>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>LogIt</artifactId>
|
||||
<version>9e3581db27</version>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.RoyalDev</groupId>
|
||||
<artifactId>RoyalAuth</artifactId>
|
||||
<version>-e21354a9b7-1</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
@@ -174,7 +176,7 @@
|
||||
<version>2.0.2</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/UltraAuth v2.0.2.jar</systemPath>
|
||||
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class BukkitCore extends FastLoginCore {
|
||||
public class BukkitCore extends FastLoginCore<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public BukkitCore(FastLoginBukkit plugin) {
|
||||
super(plugin.getConfig().getValues(false));
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@@ -44,16 +51,22 @@ public class BukkitCore extends FastLoginCore {
|
||||
|
||||
File messageFile = new File(plugin.getDataFolder(), "messages.yml");
|
||||
YamlConfiguration messageConfig = YamlConfiguration.loadConfiguration(messageFile);
|
||||
for (String key : messageConfig.getKeys(false)) {
|
||||
|
||||
InputStreamReader defaultReader = new InputStreamReader(plugin.getResource("messages.yml"), Charsets.UTF_8);
|
||||
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(defaultReader);
|
||||
|
||||
messageConfig.setDefaults(defaults);
|
||||
|
||||
messageConfig.getKeys(false).forEach((key) -> {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig() {
|
||||
plugin.saveDefaultConfig();
|
||||
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests) {
|
||||
return new MojangApiBukkit(logger, addresses, requests);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
@@ -18,10 +15,11 @@ public class BukkitLoginSession extends LoginSession {
|
||||
private final String serverId;
|
||||
private final byte[] verifyToken;
|
||||
|
||||
private UUID uuid;
|
||||
private WrappedSignedProperty skinProperty;
|
||||
private boolean verified;
|
||||
|
||||
private String encodedSkinData;
|
||||
private String skinSignature;
|
||||
|
||||
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
|
||||
, PlayerProfile profile) {
|
||||
super(username, registered, profile);
|
||||
@@ -64,22 +62,23 @@ public class BukkitLoginSession extends LoginSession {
|
||||
return ArrayUtils.clone(verifyToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the premium skin of this player
|
||||
*
|
||||
* @return skin property or null if the player has no skin or is a cracked account
|
||||
*/
|
||||
public synchronized WrappedSignedProperty getSkin() {
|
||||
return this.skinProperty;
|
||||
public synchronized String getEncodedSkinData() {
|
||||
return encodedSkinData;
|
||||
}
|
||||
|
||||
public synchronized String getSkinSignature() {
|
||||
return skinSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the premium skin property which was retrieved by the session server
|
||||
*
|
||||
* @param skinProperty premium skin property
|
||||
* @param encodedData
|
||||
* @param skinSignature
|
||||
*/
|
||||
public synchronized void setSkin(WrappedSignedProperty skinProperty) {
|
||||
this.skinProperty = skinProperty;
|
||||
public synchronized void setSkin(String encodedData, String skinSignature) {
|
||||
this.encodedSkinData = encodedData;
|
||||
this.skinSignature = skinSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,24 +90,6 @@ public class BukkitLoginSession extends LoginSession {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the premium UUID of this player
|
||||
*
|
||||
* @return the premium UUID or null if not fetched
|
||||
*/
|
||||
public synchronized UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the online UUID if it's fetched
|
||||
*
|
||||
* @param uuid premium UUID
|
||||
*/
|
||||
public synchronized void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class DefaultPasswordGenerator implements PasswordGenerator {
|
||||
|
||||
@Override
|
||||
public String getRandomPassword(Player player) {
|
||||
return RandomStringUtils.random(8, true, true);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
@@ -48,9 +49,7 @@ public class EncryptionUtil {
|
||||
private static byte[] digestOperation(String algo, byte[]... content) {
|
||||
try {
|
||||
MessageDigest messagedigest = MessageDigest.getInstance(algo);
|
||||
for (byte[] data : content) {
|
||||
messagedigest.update(data);
|
||||
}
|
||||
Stream.of(content).forEach(messagedigest::update);
|
||||
|
||||
return messagedigest.digest();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.commands.ImportCommand;
|
||||
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BukkitJoinListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BungeeCordListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.packet.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.packet.StartPacketListener;
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.LoginSkinApplyListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.StartPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@@ -30,47 +32,31 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
*/
|
||||
public class FastLoginBukkit extends JavaPlugin {
|
||||
|
||||
private static final int WORKER_THREADS = 5;
|
||||
private static final int WORKER_THREADS = 3;
|
||||
|
||||
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
|
||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
|
||||
private boolean bungeeCord;
|
||||
private final FastLoginCore core = new BukkitCore(this);
|
||||
private BukkitCore core;
|
||||
private boolean serverStarted;
|
||||
|
||||
//this map is thread-safe for async access (Packet Listener)
|
||||
//SafeCacheBuilder is used in order to be version independent
|
||||
private final ConcurrentMap<String, BukkitLoginSession> session = SafeCacheBuilder.<String, BukkitLoginSession>newBuilder()
|
||||
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
//mapped by ip:port -> PlayerSession
|
||||
.build(new CacheLoader<String, BukkitLoginSession>() {
|
||||
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
private final ConcurrentMap<String, BukkitLoginSession> session = FastLoginCore.buildCache(1, -1);
|
||||
|
||||
@Override
|
||||
public BukkitLoginSession load(String key) throws Exception {
|
||||
//A key should be inserted manually on start packet
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
});
|
||||
|
||||
private BukkitAuthPlugin authPlugin;
|
||||
private PasswordGenerator passwordGenerator = new DefaultPasswordGenerator();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
core.setMojangApiConnector(new MojangApiBukkit(core));
|
||||
|
||||
core.loadConfig();
|
||||
core.loadMessages();
|
||||
saveDefaultConfig();
|
||||
core = new BukkitCore(this);
|
||||
|
||||
core.loadMessages();
|
||||
core.setApiConnector();
|
||||
try {
|
||||
if (ClassUtil.isPresent("org.spigotmc.SpigotConfig")) {
|
||||
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
|
||||
}
|
||||
} catch (Exception | NoSuchMethodError ex) {
|
||||
getLogger().warning("Cannot check bungeecord support. You use a non-spigot build");
|
||||
ex.printStackTrace();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.WARNING, "Cannot check bungeecord support. You use a non-spigot build", ex);
|
||||
}
|
||||
|
||||
if (getServer().getOnlineMode()) {
|
||||
@@ -82,38 +68,32 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
|
||||
if (bungeeCord) {
|
||||
setServerStarted();
|
||||
|
||||
|
||||
//check for incoming messages from the bungeecord version of this plugin
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeCordListener(this));
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, getName());
|
||||
//register listeners on success
|
||||
} else {
|
||||
String driver = getConfig().getString("driver");
|
||||
String host = getConfig().getString("host", "");
|
||||
int port = getConfig().getInt("port", 3306);
|
||||
String database = getConfig().getString("database");
|
||||
|
||||
String username = getConfig().getString("username", "");
|
||||
String password = getConfig().getString("password", "");
|
||||
|
||||
if (!core.setupDatabase(driver, host, port, database, username, password)) {
|
||||
if (!core.setupDatabase()) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
|
||||
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
|
||||
} else {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
} else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
|
||||
//we are performing HTTP request on these so run it async (seperate from the Netty IO threads)
|
||||
AsynchronousManager asynchronousManager = protocolManager.getAsynchronousManager();
|
||||
AsynchronousManager asynchronousManager = ProtocolLibrary.getProtocolManager().getAsynchronousManager();
|
||||
|
||||
StartPacketListener startPacketListener = new StartPacketListener(this, protocolManager);
|
||||
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this, protocolManager);
|
||||
StartPacketListener startPacketListener = new StartPacketListener(this);
|
||||
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this);
|
||||
|
||||
asynchronousManager.registerAsyncHandler(startPacketListener).start(WORKER_THREADS);
|
||||
asynchronousManager.registerAsyncHandler(encryptionPacketListener).start(WORKER_THREADS);
|
||||
getServer().getPluginManager().registerEvents(new LoginSkinApplyListener(this), this);
|
||||
} else {
|
||||
getLogger().warning("Either ProtocolLib or ProtocolSupport have to be installed "
|
||||
+ "if you don't use BungeeCord");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,33 +105,42 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
//register commands using a unique name
|
||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||
getCommand("cracked").setExecutor(new CrackedCommand(this));
|
||||
getCommand("import-auth").setExecutor(new ImportCommand(core));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//clean up
|
||||
session.clear();
|
||||
|
||||
//remove old blacklists
|
||||
for (Player player : getServer().getOnlinePlayers()) {
|
||||
player.removeMetadata(getName(), this);
|
||||
}
|
||||
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
|
||||
//remove old blacklists
|
||||
getServer().getOnlinePlayers().forEach(player -> player.removeMetadata(getName(), this));
|
||||
}
|
||||
|
||||
public FastLoginCore getCore() {
|
||||
public BukkitCore getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
public String generateStringPassword(Player player) {
|
||||
return passwordGenerator.getRandomPassword(player);
|
||||
public void sendBungeeActivateMessage(CommandSender sender, String target, boolean activate) {
|
||||
if (sender instanceof Player) {
|
||||
notifiyBungeeCord((Player) sender, target, activate);
|
||||
} else {
|
||||
Player firstPlayer = Iterables.getFirst(getServer().getOnlinePlayers(), null);
|
||||
if (firstPlayer == null) {
|
||||
getLogger().info("No player online to send a plugin message to the proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
notifiyBungeeCord(firstPlayer, target, activate);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setPasswordGenerator(PasswordGenerator passwordGenerator) {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
core.setPasswordGenerator(passwordGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,20 +168,14 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
*
|
||||
* @return interface to any supported auth plugin
|
||||
*/
|
||||
@Deprecated
|
||||
public BukkitAuthPlugin getAuthPlugin() {
|
||||
if (authPlugin == null) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return authPlugin;
|
||||
return (BukkitAuthPlugin) core.getAuthPluginHook();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setAuthPluginHook(BukkitAuthPlugin authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
core.setAuthPluginHook(authPlugin);
|
||||
}
|
||||
|
||||
public boolean isBungeeCord() {
|
||||
@@ -214,4 +197,16 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(Player sender, String target, boolean activate) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
if (activate) {
|
||||
dataOutput.writeUTF("ON");
|
||||
} else {
|
||||
dataOutput.writeUTF("OFF");
|
||||
}
|
||||
|
||||
dataOutput.writeUTF(target);
|
||||
sender.sendPluginMessage(this, getName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.MojangApiConnector;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
@@ -19,16 +20,12 @@ public class MojangApiBukkit extends MojangApiConnector {
|
||||
//mojang api check to prove a player is logged in minecraft and made a join server request
|
||||
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
|
||||
|
||||
public MojangApiBukkit(FastLoginCore plugin) {
|
||||
super(plugin);
|
||||
public MojangApiBukkit(Logger logger, List<String> localAddresses, int rateLimit) {
|
||||
super(logger, localAddresses, rateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinedServer(Object session, String serverId) {
|
||||
if (!(session instanceof BukkitLoginSession)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasJoinedServer(LoginSession session, String serverId) {
|
||||
BukkitLoginSession playerSession = (BukkitLoginSession) session;
|
||||
try {
|
||||
String url = HAS_JOINED_URL + "username=" + playerSession.getUsername() + "&serverId=" + serverId;
|
||||
@@ -50,14 +47,14 @@ public class MojangApiBukkit extends MojangApiConnector {
|
||||
if (propertyName.equals("textures")) {
|
||||
String skinValue = (String) skinProperty.get("value");
|
||||
String signature = (String) skinProperty.get("signature");
|
||||
playerSession.setSkin(WrappedSignedProperty.fromValues(propertyName, skinValue, signature));
|
||||
playerSession.setSkin(skinValue, signature);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
//catch not only ioexceptions also parse and NPE on unexpected json format
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to verify session", ex);
|
||||
logger.log(Level.WARNING, "Failed to verify session", ex);
|
||||
}
|
||||
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
@@ -65,9 +62,22 @@ public class MojangApiBukkit extends MojangApiConnector {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getUUIDFromJson(String json) {
|
||||
JSONObject userData = (JSONObject) JSONValue.parse(json);
|
||||
String uuid = (String) userData.get("id");
|
||||
return FastLoginCore.parseId(uuid);
|
||||
protected String getUUIDFromJson(String json) {
|
||||
boolean isArray = json.startsWith("[");
|
||||
|
||||
JSONObject mojangPlayer;
|
||||
if (isArray) {
|
||||
JSONArray array = (JSONArray) JSONValue.parse(json);
|
||||
mojangPlayer = (JSONObject) array.get(0);
|
||||
} else {
|
||||
mojangPlayer = (JSONObject) JSONValue.parse(json);
|
||||
}
|
||||
|
||||
String uuid = (String) mojangPlayer.get("id");
|
||||
if ("null".equals(uuid)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,13 @@ package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface PasswordGenerator {
|
||||
/**
|
||||
*
|
||||
* @deprecated please use com.github.games647.fastlogin.core.hooks.PasswordGenerator<org.bukkit.entity.Player>
|
||||
*/
|
||||
@Deprecated
|
||||
public interface PasswordGenerator extends com.github.games647.fastlogin.core.hooks.PasswordGenerator<Player> {
|
||||
|
||||
@Override
|
||||
String getRandomPassword(Player player);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@ package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -14,7 +11,7 @@ import org.bukkit.entity.Player;
|
||||
|
||||
public class CrackedCommand implements CommandExecutor {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public CrackedCommand(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -30,86 +27,65 @@ public class CrackedCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(sender, sender.getName());
|
||||
plugin.sendBungeeActivateMessage(sender, sender.getName(), false);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if it's not in the cache anymore
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
//todo: load async if
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
|
||||
sender.sendMessage(plugin.getCore().getMessage("remove-premium"));
|
||||
|
||||
profile.setPremium(false);
|
||||
profile.setUuid(null);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You are not in the premium list");
|
||||
sender.sendMessage(plugin.getCore().getMessage("not-premium"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(sender, args[0]);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if it's not in the cache anymore
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
|
||||
profile.setPremium(false);
|
||||
profile.setUuid(null);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Player is not in the premium list");
|
||||
}
|
||||
}
|
||||
onCrackedOther(sender, command, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(CommandSender sender, String target) {
|
||||
if (sender instanceof Player) {
|
||||
notifiyBungeeCord((Player) sender, target);
|
||||
} else {
|
||||
//todo: add console support
|
||||
// Player firstPlayer = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
|
||||
// notifiyBungeeCord(firstPlayer, target);
|
||||
private void onCrackedOther(CommandSender sender, Command command, String[] args) {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(Player sender, String target) {
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
dataOutput.writeUTF("OFF");
|
||||
dataOutput.writeUTF(target);
|
||||
plugin.sendBungeeActivateMessage(sender, args[0], false);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage("Error occured");
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("No player online to send a plugin message to the proxy");
|
||||
//existing player is already cracked
|
||||
if (profile.getUserId() != -1 && !profile.isPremium()) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("not-premium-other"));
|
||||
} else {
|
||||
sender.sendMessage(plugin.getCore().getMessage("remove-premium"));
|
||||
profile.setPremium(false);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitCore;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class ImportCommand implements CommandExecutor {
|
||||
|
||||
private final BukkitCore core;
|
||||
|
||||
public ImportCommand(BukkitCore core) {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You need to specify the import plugin and database type");
|
||||
return true;
|
||||
}
|
||||
|
||||
ImportPlugin importPlugin;
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "autoin":
|
||||
importPlugin = ImportPlugin.AUTO_IN;
|
||||
break;
|
||||
case "bpa":
|
||||
importPlugin = ImportPlugin.BPA;
|
||||
break;
|
||||
case "eldzi":
|
||||
importPlugin = ImportPlugin.ELDZI;
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Unknown auto login plugin");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean sqlite;
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "sqlite":
|
||||
sqlite = true;
|
||||
break;
|
||||
case "mysql":
|
||||
sqlite = false;
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Unknown storage type to import from. Either SQLite or MySQL");
|
||||
return true;
|
||||
}
|
||||
|
||||
String host = "";
|
||||
String database = "";
|
||||
String username = "";
|
||||
String password = "";
|
||||
if (!sqlite) {
|
||||
if (args.length <= 5) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "If importing from MySQL, you need to specify host database "
|
||||
+ "and username passowrd too");
|
||||
return true;
|
||||
}
|
||||
|
||||
host = args[2];
|
||||
database = args[3];
|
||||
username = args[4];
|
||||
password = args[5];
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
boolean success = core.importDatabase(importPlugin, true, storage, host, database, username, password);
|
||||
if (success) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Successful imported the data");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Failed to import the data. Check out the logs");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@ package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -19,7 +18,7 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public class PremiumCommand implements CommandExecutor {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public PremiumCommand(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -35,88 +34,75 @@ public class PremiumCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(sender, sender.getName());
|
||||
plugin.sendBungeeActivateMessage(sender, sender.getName(), true);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
// //todo: load async if it's not in the cache anymore
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
UUID id = ((Player) sender).getUniqueId();
|
||||
if (plugin.getConfig().getBoolean("premium-warning")
|
||||
&& !plugin.getCore().getPendingConfirms().contains(id)) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
|
||||
plugin.getCore().getPendingConfirms().add(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(id);
|
||||
//todo: load async
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
|
||||
sender.sendMessage(plugin.getCore().getMessage("already-exists"));
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
|
||||
sender.sendMessage(plugin.getCore().getMessage("add-premium"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(sender, args[0]);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if it's not in the cache anymore
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Player is already on the premium list");
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
|
||||
}
|
||||
}
|
||||
onPremiumOther(sender, command, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(CommandSender sender, String target) {
|
||||
if (sender instanceof Player) {
|
||||
notifiyBungeeCord((Player) sender, target);
|
||||
} else {
|
||||
//todo: add console support
|
||||
// Player firstPlayer = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
|
||||
// notifiyBungeeCord(firstPlayer, target);
|
||||
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(Player sender, String target) {
|
||||
if (plugin.isBungeeCord()) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
dataOutput.writeUTF("ON");
|
||||
dataOutput.writeUTF(target);
|
||||
plugin.sendBungeeActivateMessage(sender, args[0], true);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("already-exists-other"));
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
|
||||
sender.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
|
||||
sender.sendMessage(plugin.getCore().getMessage("add-premium-other"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import fr.xephi.authme.api.API;
|
||||
import fr.xephi.authme.api.NewAPI;
|
||||
@@ -14,7 +15,7 @@ import org.bukkit.entity.Player;
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/authme-reloaded/
|
||||
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
|
||||
*/
|
||||
public class AuthMeHook implements BukkitAuthPlugin {
|
||||
public class AuthMeHook implements AuthPlugin<Player> {
|
||||
|
||||
private final boolean isNewAPIAvailable;
|
||||
|
||||
@@ -23,6 +24,7 @@ public class AuthMeHook implements BukkitAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean forceLogin(Player player) {
|
||||
//skips registration and login
|
||||
if (isNewAPIAvailable) {
|
||||
@@ -35,6 +37,7 @@ public class AuthMeHook implements BukkitAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
if (isNewAPIAvailable) {
|
||||
return NewAPI.getInstance().isRegistered(playerName);
|
||||
@@ -44,6 +47,7 @@ public class AuthMeHook implements BukkitAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
if (isNewAPIAvailable) {
|
||||
NewAPI.getInstance().forceRegister(player, password);
|
||||
|
||||
@@ -1,60 +1,21 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents a supporting authentication plugin in Bukkit/Spigot/... servers
|
||||
* @deprecated please use com.github.games647.fastlogin.core.hooks.AuthPlugin<org.bukkit.entity.Player>
|
||||
*/
|
||||
public interface BukkitAuthPlugin {
|
||||
@Deprecated
|
||||
public interface BukkitAuthPlugin extends AuthPlugin<Player> {
|
||||
|
||||
/**
|
||||
* Login the premium (paid account) player after
|
||||
* the player joined successfully the server.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* @param player the player that needs to be logged in
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
@Override
|
||||
boolean forceLogin(Player player);
|
||||
|
||||
/**
|
||||
* Checks whether an account exists for this player name.
|
||||
*
|
||||
* This check should check if a cracked player account exists
|
||||
* so we can be sure the premium player doesn't steal the account
|
||||
* of that player.
|
||||
*
|
||||
* This operation will be performed async while the player is
|
||||
* connecting.
|
||||
*
|
||||
* @param playerName player name
|
||||
* @return if the player has an account
|
||||
* @throws Exception if an error occurred
|
||||
*/
|
||||
@Override
|
||||
boolean isRegistered(String playerName) throws Exception;
|
||||
|
||||
/**
|
||||
* Forces a register in order to protect the paid account.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* After a successful registration the player should be logged
|
||||
* in too.
|
||||
*
|
||||
* The method will be called only for premium accounts.
|
||||
* So it's recommended to set additionally premium property
|
||||
* if possible.
|
||||
*
|
||||
* Background: If we don't register an account, cracked players
|
||||
* could steal the unregistered account from the paid
|
||||
* player account
|
||||
*
|
||||
* @param player the premium account
|
||||
* @param password a strong random generated password
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
@Override
|
||||
boolean forceRegister(Player player, String password);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import de.st_ddt.crazylogin.CrazyLogin;
|
||||
import de.st_ddt.crazylogin.data.LoginPlayerData;
|
||||
@@ -8,11 +8,11 @@ import de.st_ddt.crazylogin.databases.CrazyLoginDataDatabase;
|
||||
import de.st_ddt.crazylogin.listener.PlayerListener;
|
||||
import de.st_ddt.crazylogin.metadata.Authenticated;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.apache.commons.lang.reflect.FieldUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -23,47 +23,43 @@ import org.bukkit.entity.Player;
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/server-mods/crazylogin/
|
||||
*/
|
||||
public class CrazyLoginHook implements BukkitAuthPlugin {
|
||||
public class CrazyLoginHook implements AuthPlugin<Player> {
|
||||
|
||||
protected final CrazyLogin crazyLoginPlugin = CrazyLogin.getPlugin();
|
||||
private final CrazyLogin crazyLoginPlugin = CrazyLogin.getPlugin();
|
||||
private final PlayerListener playerListener = getListener();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe operation
|
||||
Future<LoginPlayerData> future = Bukkit.getScheduler().callSyncMethod(crazyLoginPlugin
|
||||
, new Callable<LoginPlayerData>() {
|
||||
@Override
|
||||
public LoginPlayerData call() throws Exception {
|
||||
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player.getName());
|
||||
if (playerData != null) {
|
||||
//mark the account as logged in
|
||||
playerData.setLoggedIn(true);
|
||||
Future<LoginPlayerData> future = Bukkit.getScheduler().callSyncMethod(crazyLoginPlugin, () -> {
|
||||
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player);
|
||||
if (playerData != null) {
|
||||
//mark the account as logged in
|
||||
playerData.setLoggedIn(true);
|
||||
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
//this should be done after login to restore the inventory, unhide players, prevent potential memory leaks...
|
||||
//from: https://github.com/ST-DDT/CrazyLogin/blob/master/src/main/java/de/st_ddt/crazylogin/CrazyLogin.java#L1948
|
||||
playerData.resetLoginFails();
|
||||
player.setFireTicks(0);
|
||||
playerData.resetLoginFails();
|
||||
player.setFireTicks(0);
|
||||
|
||||
if (playerListener != null) {
|
||||
playerListener.removeMovementBlocker(player);
|
||||
playerListener.disableHidenInventory(player);
|
||||
playerListener.disableSaveLogin(player);
|
||||
playerListener.unhidePlayer(player);
|
||||
}
|
||||
|
||||
//loginFailuresPerIP.remove(IP);
|
||||
//illegalCommandUsesPerIP.remove(IP);
|
||||
//tempBans.remove(IP);
|
||||
playerData.addIP(ip);
|
||||
player.setMetadata("Authenticated", new Authenticated(crazyLoginPlugin, player));
|
||||
crazyLoginPlugin.unregisterDynamicHooks();
|
||||
return playerData;
|
||||
if (playerListener != null) {
|
||||
playerListener.removeMovementBlocker(player);
|
||||
playerListener.disableHidenInventory(player);
|
||||
playerListener.disableSaveLogin(player);
|
||||
playerListener.unhidePlayer(player);
|
||||
}
|
||||
|
||||
return null;
|
||||
//loginFailuresPerIP.remove(IP);
|
||||
//illegalCommandUsesPerIP.remove(IP);
|
||||
//tempBans.remove(IP);
|
||||
playerData.addIP(ip);
|
||||
player.setMetadata("Authenticated", new Authenticated(crazyLoginPlugin, player));
|
||||
crazyLoginPlugin.unregisterDynamicHooks();
|
||||
return playerData;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -87,7 +83,7 @@ public class CrazyLoginHook implements BukkitAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(final Player player, String password) {
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
CrazyLoginDataDatabase crazyDatabase = crazyLoginPlugin.getCrazyDatabase();
|
||||
|
||||
//this executes a sql query and accesses only thread safe collections so we can run it async
|
||||
@@ -108,8 +104,8 @@ public class CrazyLoginHook implements BukkitAuthPlugin {
|
||||
private PlayerListener getListener() {
|
||||
PlayerListener listener;
|
||||
try {
|
||||
listener = FuzzyReflection.getFieldValue(crazyLoginPlugin, PlayerListener.class, true);
|
||||
} catch (Exception ex) {
|
||||
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
|
||||
} catch (IllegalAccessException ex) {
|
||||
crazyLoginPlugin.getLogger().log(Level.SEVERE, "Failed to get the listener instance for auto login", ex);
|
||||
listener = null;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import io.github.lucaseasedup.logit.CancelledState;
|
||||
import io.github.lucaseasedup.logit.LogItCore;
|
||||
import io.github.lucaseasedup.logit.account.Account;
|
||||
@@ -13,7 +15,7 @@ import org.bukkit.entity.Player;
|
||||
* Bukkit: Unknown
|
||||
* Spigot: Unknown
|
||||
*/
|
||||
public class LogItHook implements BukkitAuthPlugin {
|
||||
public class LogItHook implements AuthPlugin<Player> {
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.lenis0012.bukkit.ls.LoginSecurity;
|
||||
import com.lenis0012.bukkit.ls.data.DataManager;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.lenis0012.bukkit.loginsecurity.LoginSecurity;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.AuthService;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.PlayerSession;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.action.LoginAction;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -17,80 +14,28 @@ import org.bukkit.entity.Player;
|
||||
/**
|
||||
* Github: https://github.com/lenis0012/LoginSecurity-2 Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/loginsecurity/ Spigot:
|
||||
* https://www.spigotmc.org/resources/loginsecurity.19362/
|
||||
*
|
||||
* on join:
|
||||
* https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L282
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/loginsecurity/
|
||||
* Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/
|
||||
*/
|
||||
public class LoginSecurityHook implements BukkitAuthPlugin {
|
||||
public class LoginSecurityHook implements AuthPlugin<Player> {
|
||||
|
||||
protected final LoginSecurity securityPlugin = LoginSecurity.instance;
|
||||
// protected final boolean newVersion;
|
||||
|
||||
public LoginSecurityHook() {
|
||||
// this.newVersion = ClassUtil.isPresent("com.lenis0012.bukkit.loginsecurity.session.action.LoginAction");
|
||||
}
|
||||
private final FastLoginBukkit plugin = (FastLoginBukkit) Bukkit.getPluginManager().getPlugin("FastLogin");
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
//Login command of this plugin: (How the plugin logs the player in)
|
||||
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/commands/LoginCommand.java#L39
|
||||
|
||||
//not thread-safe operation
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(securityPlugin, new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
String name = player.getName().toLowerCase();
|
||||
|
||||
//mark the user as logged in
|
||||
securityPlugin.authList.remove(name);
|
||||
//cancel timeout timer
|
||||
securityPlugin.thread.timeout.remove(name);
|
||||
//remove effects and restore location
|
||||
securityPlugin.rehabPlayer(player, name);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
securityPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
}
|
||||
public boolean forceLogin(Player player) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L296
|
||||
DataManager dataManager = securityPlugin.data;
|
||||
|
||||
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L283
|
||||
UUID offlineUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName).getBytes(Charsets.UTF_8));
|
||||
return dataManager.isRegistered(offlineUuid.toString().replace("-", ""));
|
||||
//check for loginsecurity sessions in order to prevent a sql query?
|
||||
//sesUse && thread.getSession().containsKey(uuid) && checkLastIp(player)) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getOfflineSession(playerName);
|
||||
return session.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
DataManager dataManager = securityPlugin.data;
|
||||
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String uuidString = playerUUID.toString().replace("-", "");
|
||||
InetAddress ipAddress = player.getAddress().getAddress();
|
||||
String passwordHash = securityPlugin.hasher.hash(password);
|
||||
|
||||
//this executes a sql query without interacting with other parts so we can run it async.
|
||||
dataManager.register(uuidString, passwordHash, securityPlugin.hasher.getTypeId(), ipAddress.toString());
|
||||
String storedPassword = dataManager.getPassword(uuidString);
|
||||
if (storedPassword != null && storedPassword.equals(passwordHash)) {
|
||||
//the register method silents any excpetion so check if our entry was saved
|
||||
return forceLogin(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new RegisterAction(AuthService.PLUGIN, plugin, password)).isSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
@@ -18,24 +19,20 @@ import org.royaldev.royalauth.RoyalAuth;
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/royalauth/
|
||||
*/
|
||||
public class RoyalAuthHook implements BukkitAuthPlugin {
|
||||
public class RoyalAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final RoyalAuth royalAuthPlugin = (RoyalAuth) Bukkit.getPluginManager().getPlugin("RoyalAuth");
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(royalAuthPlugin, new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
|
||||
public boolean forceLogin(Player player) {
|
||||
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
|
||||
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(royalAuthPlugin, () -> {
|
||||
//https://github.com/RoyalDev/RoyalAuth/blob/master/src/main/java/org/royaldev/royalauth/commands/CmdLogin.java#L62
|
||||
//not thread-safe
|
||||
authPlayer.login();
|
||||
//not thread-safe
|
||||
authPlayer.login();
|
||||
|
||||
return authPlayer.isLoggedIn();
|
||||
}
|
||||
return authPlayer.isLoggedIn();
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -58,11 +55,8 @@ public class RoyalAuthHook implements BukkitAuthPlugin {
|
||||
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
|
||||
|
||||
boolean registerSuccess = authPlayer.setPassword(password, Config.passwordHashType);
|
||||
if (registerSuccess) {
|
||||
//login in the player after registration
|
||||
return forceLogin(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
//login in the player after registration
|
||||
return registerSuccess && forceLogin(player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
@@ -19,19 +20,16 @@ import ultraauth.managers.PlayerManager;
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/ultraauth-aa/
|
||||
* Spigot: https://www.spigotmc.org/resources/ultraauth.17044/
|
||||
*/
|
||||
public class UltraAuthHook implements BukkitAuthPlugin {
|
||||
public class UltraAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
protected final Plugin ultraAuthPlugin = Main.main;
|
||||
private final Plugin ultraAuthPlugin = Main.main;
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(ultraAuthPlugin, new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
UltraAuthAPI.authenticatedPlayer(player);
|
||||
return UltraAuthAPI.isAuthenticated(player);
|
||||
}
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(ultraAuthPlugin, () -> {
|
||||
UltraAuthAPI.authenticatedPlayer(player);
|
||||
return UltraAuthAPI.isAuthenticated(player);
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -44,7 +42,7 @@ public class UltraAuthHook implements BukkitAuthPlugin {
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return UltraAuthAPI.isRegisterd(new FakePlayer(playerName));
|
||||
return UltraAuthAPI.isRegisterd(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import de.luricos.bukkit.xAuth.xAuth;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
@@ -19,27 +20,24 @@ import org.bukkit.entity.Player;
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/xauth/
|
||||
*/
|
||||
public class xAuthHook implements BukkitAuthPlugin {
|
||||
public class xAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
protected final xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
private final xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
//we checked that the player is premium (paid account)
|
||||
xAuthPlayer.setPremium(true);
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, () -> {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
//we checked that the player is premium (paid account)
|
||||
xAuthPlayer.setPremium(true);
|
||||
|
||||
//unprotect the inventory, op status...
|
||||
return xAuthPlugin.getPlayerManager().doLogin(xAuthPlayer);
|
||||
}
|
||||
|
||||
return false;
|
||||
//unprotect the inventory, op status...
|
||||
return xAuthPlugin.getPlayerManager().doLogin(xAuthPlayer);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -58,33 +56,25 @@ public class xAuthHook implements BukkitAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(final Player player, final String password) {
|
||||
public boolean forceRegister(Player player, final String password) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
//this should run async because the plugin executes a sql query, but the method
|
||||
//accesses non thread-safe collections :(
|
||||
boolean registerSuccess = xAuthPlugin.getAuthClass(xAuthPlayer)
|
||||
.adminRegister(player.getName(), password, null);
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, () -> {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
//this should run async because the plugin executes a sql query, but the method
|
||||
//accesses non thread-safe collections :(
|
||||
boolean registerSuccess = xAuthPlugin.getAuthClass(xAuthPlayer)
|
||||
.adminRegister(player.getName(), password, null);
|
||||
|
||||
return registerSuccess;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
boolean success = future.get();
|
||||
if (success) {
|
||||
//login in the player after registration
|
||||
return forceLogin(player);
|
||||
return registerSuccess;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
try {
|
||||
//login in the player after registration
|
||||
return future.get() && forceLogin(player);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
xAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
|
||||
|
||||
@@ -14,6 +11,7 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
/**
|
||||
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
|
||||
@@ -23,7 +21,7 @@ public class BukkitJoinListener implements Listener {
|
||||
|
||||
private static final long DELAY_LOGIN = 20L / 2;
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public BukkitJoinListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -32,7 +30,7 @@ public class BukkitJoinListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
|
||||
loginEvent.disallow(Result.KICK_OTHER, "§cServer is not fully started yet. Please retry");
|
||||
loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,18 +38,16 @@ public class BukkitJoinListener implements Listener {
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
|
||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
WrappedSignedProperty skin = session.getSkin();
|
||||
if (skin != null) {
|
||||
gameProfile.getProperties().put("textures", skin);
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.isBungeeCord()) {
|
||||
//Wait before auth plugin and we received a message from BungeeCord initializes the player
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin, player), DELAY_LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -32,7 +32,7 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
|
||||
private static final String FILE_NAME = "proxy-whitelist.txt";
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
private final FastLoginBukkit plugin;
|
||||
//null if whitelist is empty so bungeecord support is disabled
|
||||
private final Set<UUID> proxyIds;
|
||||
|
||||
@@ -52,10 +52,10 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
plugin.getLogger().log(Level.FINEST, "Received plugin message for subchannel {0} from {1}"
|
||||
, new Object[]{subchannel, player});
|
||||
|
||||
final String playerName = dataInput.readUTF();
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
//check if the player is still online or disconnected
|
||||
final Player checkedPlayer = plugin.getServer().getPlayerExact(playerName);
|
||||
Player checkedPlayer = plugin.getServer().getPlayerExact(playerName);
|
||||
//fail if target player is blacklisted because already authed or wrong bungeecord id
|
||||
if (checkedPlayer != null && !checkedPlayer.hasMetadata(plugin.getName())) {
|
||||
//blacklist this target player for BungeeCord Id brute force attacks
|
||||
@@ -69,58 +69,46 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
|
||||
//fail if BungeeCord support is disabled (id = null)
|
||||
if (proxyIds.contains(sourceId)) {
|
||||
final String id = '/' + checkedPlayer.getAddress().getAddress().getHostAddress() + ':'
|
||||
+ checkedPlayer.getAddress().getPort();
|
||||
if ("AUTO_LOGIN".equalsIgnoreCase(subchannel)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
} else if ("AUTO_REGISTER".equalsIgnoreCase(subchannel)) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
try {
|
||||
//we need to check if the player is registered on Bukkit too
|
||||
if (authPlugin != null && !authPlugin.isRegistered(playerName)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new ForceLoginTask(plugin, player));
|
||||
readMessage(checkedPlayer, subchannel, playerName, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readMessage(Player checkedPlayer, String subchannel, String playerName, Player player) {
|
||||
InetSocketAddress address = checkedPlayer.getAddress();
|
||||
String id = '/' + address.getAddress().getHostAddress() + ':' + address.getPort();
|
||||
if ("AUTO_LOGIN".equalsIgnoreCase(subchannel)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new ForceLoginTask(plugin, player));
|
||||
} else if ("AUTO_REGISTER".equalsIgnoreCase(subchannel)) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
try {
|
||||
//we need to check if the player is registered on Bukkit too
|
||||
if (authPlugin == null || !authPlugin.isRegistered(playerName)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
new ForceLoginTask(plugin, player).run();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Set<UUID> loadBungeeCordIds() {
|
||||
File whitelistFile = new File(plugin.getDataFolder(), FILE_NAME);
|
||||
//create a new folder if it doesn't exist. Fail silently otherwise
|
||||
whitelistFile.getParentFile().mkdir();
|
||||
try {
|
||||
if (!whitelistFile.exists()) {
|
||||
whitelistFile.createNewFile();
|
||||
}
|
||||
|
||||
Set<UUID> ids = Sets.newHashSet();
|
||||
|
||||
List<String> lines = Files.readLines(whitelistFile, Charsets.UTF_8);
|
||||
for (String line : lines) {
|
||||
if (line == null || line.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UUID uuid = UUID.fromString(line.trim());
|
||||
ids.add(uuid);
|
||||
}
|
||||
|
||||
return ids;
|
||||
List<String> lines = Files.readAllLines(whitelistFile.toPath());
|
||||
return lines.stream().map(String::trim).map(UUID::fromString).collect(Collectors.toSet());
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to create file for Proxy whitelist", ex);
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
|
||||
|
||||
public class ProtocolSupportListener implements Listener {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
plugin.setServerStarted();
|
||||
if (loginStartEvent.isLoginDenied()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getName();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.getSessions().remove(loginStartEvent.getAddress().toString());
|
||||
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
||||
if (profile != null) {
|
||||
if (profile.getUserId() == -1) {
|
||||
UUID premiumUUID = null;
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
//user not exists in the db
|
||||
try {
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
startPremiumSession(username, loginStartEvent, false, profile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (premiumUUID != null
|
||||
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
startPremiumSession(username, loginStartEvent, false, profile);
|
||||
return;
|
||||
}
|
||||
|
||||
//no premium check passed so we save it as a cracked player
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(loginStartEvent.getAddress().toString(), loginSession);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
startPremiumSession(username, loginStartEvent, true, profile);
|
||||
} else {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(loginStartEvent.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPropertiesResolve(PlayerPropertiesResolveEvent propertiesResolveEvent) {
|
||||
//skin was resolved -> premium player
|
||||
if (propertiesResolveEvent.hasProperty("textures")) {
|
||||
InetSocketAddress address = propertiesResolveEvent.getAddress();
|
||||
BukkitLoginSession session = plugin.getSessions().get(address.toString());
|
||||
if (session != null) {
|
||||
session.setVerified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered
|
||||
, PlayerProfile playerProfile) {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, null, null, registered, playerProfile);
|
||||
plugin.getSessions().put(address.toString(), playerSession);
|
||||
if (plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
loginStartEvent.setUseOnlineModeUUID(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
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.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Handles incoming start packets from connecting clients. It
|
||||
* checks if we can start checking if the player is premium and
|
||||
* start a request to the client that it should start online mode
|
||||
* login.
|
||||
*
|
||||
* Receiving packet information:
|
||||
* http://wiki.vg/Protocol#Login_Start
|
||||
*
|
||||
* String=Username
|
||||
*/
|
||||
public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
//hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final Random random = new Random();
|
||||
|
||||
public StartPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
||||
//run async in order to not block the server, because we are making api calls to Mojang
|
||||
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.protocolManager = protocolManger;
|
||||
}
|
||||
|
||||
/**
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
plugin.setServerStarted();
|
||||
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
//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.getSessions().remove(sessionKey);
|
||||
|
||||
//player.getName() won't work at this state
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
|
||||
, new Object[]{sessionKey, username});
|
||||
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
||||
if (profile != null) {
|
||||
if (profile.getUserId() == -1) {
|
||||
UUID premiumUUID = null;
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
//user not exists in the db
|
||||
try {
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (premiumUUID != null
|
||||
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
|
||||
return;
|
||||
}
|
||||
|
||||
//no premium check passed so we save it as a cracked player
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(sessionKey, loginSession);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, true);
|
||||
} else {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(sessionKey, loginSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
private void enablePremiumLogin(String username, PlayerProfile profile, String sessionKey, Player player
|
||||
, PacketEvent packetEvent, boolean registered) {
|
||||
//randomized server id to make sure the request is for our server
|
||||
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
String serverId = Long.toString(random.nextLong(), 16);
|
||||
|
||||
//generate a random token which should be the same when we receive it from the client
|
||||
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
||||
random.nextBytes(verifyToken);
|
||||
|
||||
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
|
||||
if (success) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId
|
||||
, verifyToken, registered, profile);
|
||||
plugin.getSessions().put(sessionKey, playerSession);
|
||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
||||
try {
|
||||
/**
|
||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||
*
|
||||
* ServerID="" (String)
|
||||
* key=public server key
|
||||
* verifyToken=random 4 byte array
|
||||
*/
|
||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
||||
|
||||
newPacket.getStrings().write(0, serverId);
|
||||
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
|
||||
|
||||
newPacket.getByteArrays().write(0, verifyToken);
|
||||
|
||||
//serverId is a empty string
|
||||
protocolManager.sendServerPacket(player, newPacket);
|
||||
return true;
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
|
||||
/**
|
||||
* Handles incoming encryption responses from connecting clients.
|
||||
* It prevents them from reaching the server because that cannot handle
|
||||
* it in offline mode.
|
||||
*
|
||||
* Moreover this manages a started premium check from
|
||||
* this plugin. So check if all data is correct and we can prove him as a
|
||||
* owner of a paid minecraft account.
|
||||
*
|
||||
* Receiving packet information:
|
||||
* http://wiki.vg/Protocol#Encryption_Response
|
||||
*
|
||||
* sharedSecret=encrypted byte array
|
||||
* verify token=encrypted byte array
|
||||
*/
|
||||
public class EncryptionPacketListener extends PacketAdapter {
|
||||
|
||||
//hides the inherit Plugin plugin field, but we need this type
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public EncryptionPacketListener(FastLoginBukkit plugin) {
|
||||
//run async in order to not block the server, because we make api calls to Mojang
|
||||
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*
|
||||
* Minecraft Server implementation
|
||||
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L180
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player sender = packetEvent.getPlayer();
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
VerifyResponseTask verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
|
||||
public class LoginSkinApplyListener 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 LoginSkinApplyListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
//run this on the loginEvent to let skins plugins see the skin like in normal minecraft behaviour
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
if (loginEvent.getResult() != Result.ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = loginEvent.getPlayer();
|
||||
|
||||
if (plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
//go through every session, because player.getAddress is null
|
||||
//loginEvent.getAddress is just a InetAddress not InetSocketAddres, so not unique enough
|
||||
for (BukkitLoginSession session : plugin.getSessions().values()) {
|
||||
if (session.getUsername().equals(player.getName())) {
|
||||
String signature = session.getSkinSignature();
|
||||
String skinData = session.getEncodedSkinData();
|
||||
|
||||
applySkin(player, skinData, signature);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySkin(Player player, String skinData, String signature) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
if (skinData != null && signature != null) {
|
||||
WrappedSignedProperty skin = WrappedSignedProperty.fromValues("textures", skinData, signature);
|
||||
try {
|
||||
gameProfile.getProperties().put("textures", skin);
|
||||
} catch (ClassCastException castException) {
|
||||
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
|
||||
try {
|
||||
MethodUtils.invokeMethod(map, "put", new Object[]{"textures", skin.getHandle()});
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium skin", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NameCheckTask extends JoinManagement<Player, ProtocolLibLoginSource> implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private final Player player;
|
||||
private final String username;
|
||||
|
||||
public NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random, Player player, String username) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.random = random;
|
||||
this.player = player;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
super.onLogin(username, new ProtocolLibLoginSource(plugin, packetEvent, player, random));
|
||||
} finally {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLibLoginSource source, PlayerProfile profile, String username, boolean registered) {
|
||||
try {
|
||||
source.setOnlineMode();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to cracked login", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
core.getPendingLogins().put(ip + username, new Object());
|
||||
|
||||
String serverId = source.getServerId();
|
||||
byte[] verify = source.getVerifyToken();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
|
||||
plugin.getSessions().put(player.getAddress().toString(), playerSession);
|
||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLibLoginSource source, PlayerProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(player.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ProtocolLibLoginSource implements LoginSource {
|
||||
|
||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
private final PacketEvent packetEvent;
|
||||
private final Player player;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private String serverId;
|
||||
private final byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
||||
|
||||
public ProtocolLibLoginSource(FastLoginBukkit plugin, PacketEvent packetEvent, Player player, Random random) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.player = player;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() throws Exception {
|
||||
//randomized server id to make sure the request is for our server
|
||||
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
serverId = Long.toString(random.nextLong(), 16);
|
||||
|
||||
//generate a random token which should be the same when we receive it from the client
|
||||
random.nextBytes(verifyToken);
|
||||
|
||||
sentEncryptionRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) throws Exception {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.DISCONNECT);
|
||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(message));
|
||||
|
||||
try {
|
||||
//send kick packet at login state
|
||||
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
|
||||
protocolManager.sendServerPacket(player, kickPacket);
|
||||
} finally {
|
||||
//tell the server that we want to close the connection
|
||||
player.kickPlayer("Disconnect");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return packetEvent.getPlayer().getAddress();
|
||||
}
|
||||
|
||||
private void sentEncryptionRequest() throws InvocationTargetException {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
/**
|
||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||
*
|
||||
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
|
||||
*/
|
||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
||||
|
||||
newPacket.getStrings().write(0, serverId);
|
||||
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
|
||||
|
||||
newPacket.getByteArrays().write(0, verifyToken);
|
||||
|
||||
//serverId is a empty string
|
||||
protocolManager.sendServerPacket(player, newPacket);
|
||||
}
|
||||
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Handles incoming start packets from connecting clients. It
|
||||
* checks if we can start checking if the player is premium and
|
||||
* start a request to the client that it should start online mode
|
||||
* login.
|
||||
*
|
||||
* Receiving packet information:
|
||||
* http://wiki.vg/Protocol#Login_Start
|
||||
*
|
||||
* String=Username
|
||||
*/
|
||||
public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
//hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final Random random = new Random();
|
||||
|
||||
public StartPacketListener(FastLoginBukkit plugin) {
|
||||
//run async in order to not block the server, because we are making api calls to Mojang
|
||||
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled()
|
||||
|| plugin.getCore().getAuthPluginHook()== null || !plugin.isServerFullyStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
//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.getSessions().remove(sessionKey);
|
||||
|
||||
//player.getName() won't work at this state
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting", new Object[]{sessionKey, username});
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
NameCheckTask nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
@@ -13,7 +15,6 @@ import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
@@ -26,65 +27,50 @@ import javax.crypto.SecretKey;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Handles incoming encryption responses from connecting clients.
|
||||
* It prevents them from reaching the server because that cannot handle
|
||||
* it in offline mode.
|
||||
*
|
||||
* Moreover this manages a started premium check from
|
||||
* this plugin. So check if all data is correct and we can prove him as a
|
||||
* owner of a paid minecraft account.
|
||||
*
|
||||
* Receiving packet information:
|
||||
* http://wiki.vg/Protocol#Encryption_Response
|
||||
*
|
||||
* sharedSecret=encrypted byte array
|
||||
* verify token=encrypted byte array
|
||||
*/
|
||||
public class EncryptionPacketListener extends PacketAdapter {
|
||||
public class VerifyResponseTask implements Runnable {
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
//hides the inherit Plugin plugin field, but we need this type
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
|
||||
public EncryptionPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
||||
//run async in order to not block the server, because we make api calls to Mojang
|
||||
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
||||
private final Player fromPlayer;
|
||||
|
||||
private final byte[] sharedSecret;
|
||||
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player fromPlayer, byte[] sharedSecret) {
|
||||
this.plugin = plugin;
|
||||
this.protocolManager = protocolManger;
|
||||
this.packetEvent = packetEvent;
|
||||
this.fromPlayer = fromPlayer;
|
||||
this.sharedSecret = sharedSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*
|
||||
* Minecraft Server implementation
|
||||
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L180
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
Player player = packetEvent.getPlayer();
|
||||
public void run() {
|
||||
try {
|
||||
BukkitLoginSession session = plugin.getSessions().get(fromPlayer.getAddress().toString());
|
||||
if (session == null) {
|
||||
disconnect(plugin.getCore().getMessage("invalid-requst"), true
|
||||
, "Player {0} tried to send encryption response at invalid state", fromPlayer.getAddress());
|
||||
} else {
|
||||
String ip = fromPlayer.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().remove(ip + session.getUsername());
|
||||
|
||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
if (session == null) {
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-requst"), true
|
||||
, "Player {0} tried to send encryption response at invalid state", player.getAddress());
|
||||
return;
|
||||
verifyResponse(session);
|
||||
}
|
||||
} finally {
|
||||
//this is a fake packet; it shouldn't be send to the server
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyResponse(BukkitLoginSession session) {
|
||||
PrivateKey privateKey = plugin.getServerKey().getPrivate();
|
||||
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
||||
if (!checkVerifyToken(session, privateKey, packetEvent) || !encryptConnection(player, loginKey, packetEvent)) {
|
||||
if (!checkVerifyToken(session, privateKey) || !encryptConnection(loginKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,38 +84,33 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
||||
|
||||
String username = session.getUsername();
|
||||
if (plugin.getCore().getMojangApiConnector().hasJoinedServer(session, serverId)) {
|
||||
if (plugin.getCore().getApiConnector().hasJoinedServer(session, serverId)) {
|
||||
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||
|
||||
session.setVerified(true);
|
||||
setPremiumUUID(session, player);
|
||||
receiveFakeStartPacket(username, player);
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(username);
|
||||
} else {
|
||||
//user tried to fake a authentication
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-session"), true
|
||||
disconnect(plugin.getCore().getMessage("invalid-session"), true
|
||||
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
||||
, session.getUsername(), player.getAddress(), serverId);
|
||||
, session.getUsername(), fromPlayer.getAddress(), serverId);
|
||||
}
|
||||
|
||||
//this is a fake packet; it shouldn't be send to the server
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
private void setPremiumUUID(BukkitLoginSession session, Player player) {
|
||||
UUID uuid = session.getUuid();
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && uuid != null) {
|
||||
private void setPremiumUUID(UUID premiumUUID) {
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
||||
try {
|
||||
Object networkManager = getNetworkManager(player);
|
||||
Object networkManager = getNetworkManager();
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
|
||||
Field spoofField = FuzzyReflection.fromObject(networkManager).getFieldByType("spoofedUUID", UUID.class);
|
||||
spoofField.set(networkManager, uuid);
|
||||
} catch (ReflectiveOperationException reflectiveOperationException) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium uuid", reflectiveOperationException);
|
||||
FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true);
|
||||
} catch (Exception exc) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium uuid", exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey, PacketEvent packetEvent) {
|
||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey) {
|
||||
byte[] requestVerify = session.getVerifyToken();
|
||||
//encrypted verify token
|
||||
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||
@@ -137,7 +118,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
||||
if (!Arrays.equals(requestVerify, EncryptionUtil.decryptData(privateKey, responseVerify))) {
|
||||
//check if the verify token are equal to the server sent one
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-verify-token"), true
|
||||
disconnect(plugin.getCore().getMessage("invalid-verify-token"), true
|
||||
, "Player {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
|
||||
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
|
||||
return false;
|
||||
@@ -147,54 +128,48 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
//try to get the networkManager from ProtocolLib
|
||||
private Object getNetworkManager(Player player)
|
||||
throws IllegalAccessException, NoSuchFieldException {
|
||||
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
Field injectorField = socketInjector.getClass().getDeclaredField("injector");
|
||||
injectorField.setAccessible(true);
|
||||
private Object getNetworkManager() throws IllegalAccessException, NoSuchFieldException {
|
||||
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(fromPlayer);
|
||||
|
||||
Object rawInjector = injectorField.get(socketInjector);
|
||||
|
||||
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||
injectorField.setAccessible(true);
|
||||
return injectorField.get(rawInjector);
|
||||
//ChannelInjector
|
||||
Injector rawInjector = FuzzyReflection.getFieldValue(injectorContainer, Injector.class, true);
|
||||
return FieldUtils.readField(rawInjector, "networkManager", true);
|
||||
}
|
||||
|
||||
private boolean encryptConnection(Player player, SecretKey loginKey, PacketEvent packetEvent)
|
||||
throws IllegalArgumentException {
|
||||
private boolean encryptConnection(SecretKey loginKey) throws IllegalArgumentException {
|
||||
try {
|
||||
//get the NMS connection handle of this player
|
||||
Object networkManager = getNetworkManager(player);
|
||||
Object networkManager = getNetworkManager();
|
||||
|
||||
//try to detect the method by parameters
|
||||
Method encryptConnectionMethod = FuzzyReflection
|
||||
Method encryptMethod = FuzzyReflection
|
||||
.fromObject(networkManager).getMethodByParameters("a", SecretKey.class);
|
||||
|
||||
//encrypt/decrypt following packets
|
||||
//the client expects this behaviour
|
||||
encryptConnectionMethod.invoke(networkManager, loginKey);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
|
||||
encryptMethod.invoke(networkManager, loginKey);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Couldn't enable encryption", ex);
|
||||
disconnect(plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void disconnect(PacketEvent packetEvent, String kickReason, boolean debugLevel, String logMessage
|
||||
, Object... arguments) {
|
||||
if (debugLevel) {
|
||||
private void disconnect(String kickReason, boolean debug, String logMessage, Object... arguments) {
|
||||
if (debug) {
|
||||
plugin.getLogger().log(Level.FINE, logMessage, arguments);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, logMessage, arguments);
|
||||
}
|
||||
|
||||
kickPlayer(packetEvent.getPlayer(), kickReason);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
private void kickPlayer(Player player, String reason) {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.DISCONNECT);
|
||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
|
||||
|
||||
@@ -210,7 +185,9 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
//fake a new login packet in order to let the server handle all the other stuff
|
||||
private void receiveFakeStartPacket(String username, Player from) {
|
||||
private void receiveFakeStartPacket(String username) {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
//see StartPacketListener for packet information
|
||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START);
|
||||
|
||||
@@ -219,11 +196,11 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
//we don't want to handle our own packets so ignore filters
|
||||
protocolManager.recieveClientPacket(from, startPacket, false);
|
||||
protocolManager.recieveClientPacket(fromPlayer, startPacket, false);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", ex);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
kickPlayer(from, plugin.getCore().getMessage("error-kick"));
|
||||
kickPlayer(fromPlayer, plugin.getCore().getMessage("error-kick"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
|
||||
public class ProtocolLoginSource implements LoginSource {
|
||||
|
||||
private final PlayerLoginStartEvent loginStartEvent;
|
||||
|
||||
public ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
|
||||
this.loginStartEvent = loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
loginStartEvent.denyLogin(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return loginStartEvent.getAddress();
|
||||
}
|
||||
|
||||
public PlayerLoginStartEvent getLoginStartEvent() {
|
||||
return loginStartEvent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
|
||||
|
||||
public class ProtocolSupportListener extends JoinManagement<Player, ProtocolLoginSource> implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
plugin.setServerStarted();
|
||||
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getName();
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.getSessions().remove(address.toString());
|
||||
|
||||
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPropertiesResolve(PlayerPropertiesResolveEvent propertiesResolveEvent) {
|
||||
InetSocketAddress address = propertiesResolveEvent.getAddress();
|
||||
BukkitLoginSession session = plugin.getSessions().get(address.toString());
|
||||
|
||||
//skin was resolved -> premium player
|
||||
if (propertiesResolveEvent.hasProperty("textures") && session != null) {
|
||||
String ip = address.getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().remove(ip + session.getUsername());
|
||||
|
||||
session.setVerified(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLoginSource source, PlayerProfile profile, String username, boolean registered) {
|
||||
source.setOnlineMode();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().put(ip + username, new Object());
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, null, null, registered, profile);
|
||||
plugin.getSessions().put(source.getAddress().toString(), playerSession);
|
||||
if (plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
source.getLoginStartEvent().setUseOnlineModeUUID(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLoginSource source, PlayerProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(source.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,19 @@ package com.github.games647.fastlogin.bukkit.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.AuthMeHook;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.CrazyLoginHook;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.LogItHook;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.LoginSecurityHook;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.UltraAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.xAuthHook;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class DelayedAuthHook implements Runnable {
|
||||
|
||||
@@ -37,12 +38,14 @@ public class DelayedAuthHook implements Runnable {
|
||||
}
|
||||
|
||||
private boolean registerHooks() {
|
||||
BukkitAuthPlugin authPluginHook = null;
|
||||
AuthPlugin<Player> authPluginHook = null;
|
||||
try {
|
||||
List<Class<? extends BukkitAuthPlugin>> supportedHooks = Lists.newArrayList(AuthMeHook.class
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Class<? extends AuthPlugin<Player>>> supportedHooks = Lists.newArrayList(AuthMeHook.class
|
||||
, CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class
|
||||
, xAuthHook.class);
|
||||
for (Class<? extends BukkitAuthPlugin> clazz : supportedHooks) {
|
||||
|
||||
for (Class<? extends AuthPlugin<Player>> clazz : supportedHooks) {
|
||||
String pluginName = clazz.getSimpleName().replace("Hook", "");
|
||||
//uses only member classes which uses AuthPlugin interface (skip interfaces)
|
||||
if (Bukkit.getServer().getPluginManager().getPlugin(pluginName) != null) {
|
||||
@@ -62,8 +65,9 @@ public class DelayedAuthHook implements Runnable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.getAuthPlugin() == null) {
|
||||
plugin.setAuthPluginHook(authPluginHook);
|
||||
if (plugin.getCore().getAuthPluginHook() == null) {
|
||||
plugin.getCore().setAuthPluginHook(authPluginHook);
|
||||
plugin.setServerStarted();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,13 +2,12 @@ package com.github.games647.fastlogin.bukkit.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.Storage;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
@@ -19,7 +18,7 @@ import org.bukkit.entity.Player;
|
||||
public class ForceLoginTask implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
protected final Player player;
|
||||
private final Player player;
|
||||
|
||||
public ForceLoginTask(FastLoginBukkit plugin, Player player) {
|
||||
this.plugin = plugin;
|
||||
@@ -39,23 +38,27 @@ public class ForceLoginTask implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
Storage storage = plugin.getCore().getStorage();
|
||||
AuthStorage storage = plugin.getCore().getStorage();
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
//check if it's the same player as we checked before
|
||||
if (session.isVerified() && player.getName().equals(session.getUsername())) {
|
||||
//premium player
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//maybe only bungeecord plugin
|
||||
sendSuccessNotification();
|
||||
} else {
|
||||
boolean success = false;
|
||||
if (isOnlineThreadSafe()) {
|
||||
if (session.needsRegistration()) {
|
||||
success = forceRegister(authPlugin, player);
|
||||
if (plugin.getConfig().getBoolean("autoLogin")) {
|
||||
if (session.needsRegistration()) {
|
||||
success = forceRegister(authPlugin, player);
|
||||
} else {
|
||||
success = forceLogin(authPlugin, player);
|
||||
}
|
||||
} else {
|
||||
success = forceLogin(authPlugin, player);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +67,7 @@ public class ForceLoginTask implements Runnable {
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setUuid(session.getUuid());
|
||||
//save cracked players too
|
||||
playerProfile.setPremium(session.isVerified());
|
||||
playerProfile.setPremium(true);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
|
||||
@@ -81,13 +84,13 @@ public class ForceLoginTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forceRegister(BukkitAuthPlugin authPlugin, Player player) {
|
||||
private boolean forceRegister(AuthPlugin<Player> authPlugin, Player player) {
|
||||
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
|
||||
|
||||
String generatedPassword = plugin.generateStringPassword(player);
|
||||
String generatedPassword = plugin.getCore().getPasswordGenerator().getRandomPassword(player);
|
||||
boolean success = authPlugin.forceRegister(player, generatedPassword);
|
||||
String message = plugin.getCore().getMessage("auto-register");
|
||||
if (message != null) {
|
||||
if (success && message != null) {
|
||||
message = message.replace("%password", generatedPassword);
|
||||
player.sendMessage(message);
|
||||
}
|
||||
@@ -95,12 +98,12 @@ public class ForceLoginTask implements Runnable {
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean forceLogin(BukkitAuthPlugin authPlugin, Player player) {
|
||||
private boolean forceLogin(AuthPlugin<Player> authPlugin, Player player) {
|
||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||
boolean success = authPlugin.forceLogin(player);
|
||||
|
||||
String message = plugin.getCore().getMessage("auto-login");
|
||||
if (message != null) {
|
||||
if (success && message != null) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
@@ -118,12 +121,7 @@ public class ForceLoginTask implements Runnable {
|
||||
|
||||
private boolean isOnlineThreadSafe() {
|
||||
//the playerlist isn't thread-safe
|
||||
Future<Boolean> onlineFuture = Bukkit.getScheduler().callSyncMethod(plugin, new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
return player.isOnline();
|
||||
}
|
||||
});
|
||||
Future<Boolean> onlineFuture = Bukkit.getScheduler().callSyncMethod(plugin, player::isOnline);
|
||||
|
||||
try {
|
||||
return onlineFuture.get();
|
||||
|
||||
@@ -14,19 +14,10 @@ dev-url: ${project.url}
|
||||
# Load the plugin as early as possible to inject it for all players
|
||||
load: STARTUP
|
||||
|
||||
# Without Protocollib the plugin does not work at all
|
||||
depend: [ProtocolLib]
|
||||
|
||||
softdepend:
|
||||
# We depend either ProtocolLib or ProtocolSupport
|
||||
- ProtocolSupport
|
||||
# Auth plugins
|
||||
# - xAuth
|
||||
# - AuthMe
|
||||
# - LogIt
|
||||
# - CrazyLogin
|
||||
# - LoginSecurity
|
||||
# - RoyalAuth
|
||||
# - UltraAuth
|
||||
- ProtocolLib
|
||||
|
||||
commands:
|
||||
${project.parent.name}:
|
||||
@@ -39,7 +30,12 @@ commands:
|
||||
description: 'Label the invoker or the player specified as cracked if he was marked premium before'
|
||||
aliases: [unpremium]
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.unpremium
|
||||
permission: ${project.artifactId}.command.cracked
|
||||
|
||||
import-auth:
|
||||
description: 'Imports the auth data from another auto login'
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.import
|
||||
|
||||
permissions:
|
||||
${project.artifactId}.command.premium:
|
||||
@@ -55,7 +51,7 @@ permissions:
|
||||
description: 'Label themselves as cracked'
|
||||
default: true
|
||||
|
||||
${project.artifactId}.command..cracked.other:
|
||||
${project.artifactId}.command.cracked.other:
|
||||
description: 'Label others as cracked'
|
||||
children:
|
||||
${project.artifactId}.command.cracked: true
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -19,14 +19,13 @@
|
||||
<repositories>
|
||||
<!--BungeeCord with also the part outside the API-->
|
||||
<repository>
|
||||
<id>RYRED-REPO</id>
|
||||
<url>http://mvn.ryred.co/repository/snapshots/</url>
|
||||
<id>myplayplanet-REPO</id>
|
||||
<url>http://maven.myplayplanet.net/</url>
|
||||
</repository>
|
||||
|
||||
<!--Github automatic maven builds-->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
<id>vik1395-repo</id>
|
||||
<url>http://repo.vik1395.me/repositories</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
@@ -41,21 +40,14 @@
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>1.9-SNAPSHOT</version>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>io.github.waterfallmc</groupId>
|
||||
<artifactId>waterfall-api</artifactId>
|
||||
<version>1.9-SNAPSHOT</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.MatteCarra</groupId>
|
||||
<groupId>me.vik1395</groupId>
|
||||
<artifactId>BungeeAuth</artifactId>
|
||||
<version>-1.2.1-gc367d92-8</version>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
public class BungeeCore extends FastLoginCore {
|
||||
public class BungeeCore extends FastLoginCore<ProxiedPlayer> {
|
||||
|
||||
private static Map<String, Object> generateConfigMap(Configuration config) {
|
||||
return config.getKeys().stream()
|
||||
.filter(key -> config.get(key) != null)
|
||||
.collect(Collectors.toMap(key -> key, config::get));
|
||||
}
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public BungeeCore(FastLoginBungee plugin) {
|
||||
public BungeeCore(FastLoginBungee plugin, Configuration config) {
|
||||
super(generateConfigMap(config));
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@@ -36,50 +47,41 @@ public class BungeeCore extends FastLoginCore {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public ThreadFactory getThreadFactory() {
|
||||
String pluginName = plugin.getDescription().getName();
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(pluginName + " Database Pool Thread #%1$d")
|
||||
//Hikari create daemons by default
|
||||
.setDaemon(true)
|
||||
.setThreadFactory(new GroupedThreadFactory(plugin, pluginName)).build();
|
||||
.setThreadFactory(new GroupedThreadFactory(plugin, pluginName))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessages() {
|
||||
try {
|
||||
saveDefaultFile("messages.yml");
|
||||
|
||||
plugin.saveDefaultFile("messages.yml");
|
||||
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
|
||||
Configuration defaults = configProvider.load(getClass().getResourceAsStream("/messages.yml"));
|
||||
|
||||
File messageFile = new File(getDataFolder(), "messages.yml");
|
||||
Configuration messageConfig = ConfigurationProvider.getProvider(YamlConfiguration.class).load(messageFile);
|
||||
for (String key : messageConfig.getKeys()) {
|
||||
Configuration messageConfig = configProvider.load(messageFile, defaults);
|
||||
|
||||
messageConfig.getKeys().forEach(key -> {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, "Failed to load messages", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig() {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
saveDefaultFile("config.yml");
|
||||
}
|
||||
|
||||
private void saveDefaultFile(String fileName) {
|
||||
File configFile = new File(getDataFolder(), fileName);
|
||||
if (!configFile.exists()) {
|
||||
try (InputStream in = plugin.getResourceAsStream(fileName)) {
|
||||
Files.copy(in, configFile.toPath());
|
||||
} catch (IOException ioExc) {
|
||||
getLogger().log(Level.SEVERE, "Error saving default " + fileName, ioExc);
|
||||
}
|
||||
}
|
||||
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests) {
|
||||
return new MojangApiBungee(logger, addresses, requests);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
public class BungeeLoginSession extends LoginSession {
|
||||
|
||||
private boolean alreadySaved;
|
||||
private boolean alreadyLogged;
|
||||
|
||||
public BungeeLoginSession(String username, boolean registered, PlayerProfile profile) {
|
||||
super(username, registered, profile);
|
||||
@@ -22,4 +23,12 @@ public class BungeeLoginSession extends LoginSession {
|
||||
public void setAlreadySaved(boolean alreadySaved) {
|
||||
this.alreadySaved = alreadySaved;
|
||||
}
|
||||
|
||||
public boolean isAlreadyLogged() {
|
||||
return alreadyLogged;
|
||||
}
|
||||
|
||||
public void setAlreadyLogged(boolean alreadyLogged) {
|
||||
this.alreadyLogged = alreadyLogged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
|
||||
public class BungeeLoginSource implements LoginSource {
|
||||
|
||||
private final PendingConnection connection;
|
||||
|
||||
public BungeeLoginSource(PendingConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() {
|
||||
connection.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
connection.disconnect(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return connection.getAddress();
|
||||
}
|
||||
|
||||
public PendingConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import com.github.games647.fastlogin.bungee.hooks.BungeeAuthHook;
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.github.games647.fastlogin.bungee.listener.PlayerConnectionListener;
|
||||
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -24,36 +24,23 @@ import net.md_5.bungee.config.YamlConfiguration;
|
||||
*/
|
||||
public class FastLoginBungee extends Plugin {
|
||||
|
||||
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
.toCharArray();
|
||||
|
||||
private final FastLoginCore loginCore = new BungeeCore(this);
|
||||
private BungeeAuthPlugin bungeeAuthPlugin;
|
||||
private Configuration configuration;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
private final ConcurrentMap<PendingConnection, BungeeLoginSession> session = Maps.newConcurrentMap();
|
||||
|
||||
private BungeeCore core;
|
||||
private Configuration config;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
loginCore.setMojangApiConnector(new MojangApiBungee(loginCore));
|
||||
|
||||
loginCore.loadConfig();
|
||||
loginCore.loadMessages();
|
||||
saveDefaultFile("config.yml");
|
||||
|
||||
try {
|
||||
File configFile = new File(getDataFolder(), "config.yml");
|
||||
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
|
||||
ConfigurationProvider provider = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
Configuration defaults = provider.load(getResourceAsStream("config.yml"));
|
||||
config = provider.load(configFile, defaults);
|
||||
|
||||
String driver = configuration.getString("driver");
|
||||
String host = configuration.getString("host", "");
|
||||
int port = configuration.getInt("port", 3306);
|
||||
String database = configuration.getString("database");
|
||||
|
||||
String username = configuration.getString("username", "");
|
||||
String password = configuration.getString("password", "");
|
||||
if (!loginCore.setupDatabase(driver, host, port, database, username, password)) {
|
||||
core = new BungeeCore(this, config);
|
||||
if (!core.setupDatabase()) {
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioExc) {
|
||||
@@ -61,40 +48,62 @@ public class FastLoginBungee extends Plugin {
|
||||
return;
|
||||
}
|
||||
|
||||
core.setApiConnector();
|
||||
core.loadMessages();
|
||||
|
||||
//events
|
||||
getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this));
|
||||
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
//bungee only commands
|
||||
getProxy().getPluginManager().registerCommand(this, new ImportCommand(this));
|
||||
|
||||
//this is required to listen to messages from the server
|
||||
getProxy().registerChannel(getDescription().getName());
|
||||
|
||||
registerHook();
|
||||
}
|
||||
|
||||
public String generateStringPassword() {
|
||||
StringBuilder generatedPassword = new StringBuilder(8);
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
generatedPassword.append(PASSWORD_CHARACTERS[random.nextInt(PASSWORD_CHARACTERS.length - 1)]);
|
||||
}
|
||||
|
||||
return generatedPassword.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
loginCore.close();
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
}
|
||||
|
||||
public FastLoginCore getCore() {
|
||||
return loginCore;
|
||||
public void saveDefaultFile(String fileName) {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
File configFile = new File(getDataFolder(), fileName);
|
||||
if (!configFile.exists()) {
|
||||
InputStream in = getResourceAsStream(fileName);
|
||||
try {
|
||||
Files.copy(in, configFile.toPath());
|
||||
} catch (IOException ioExc) {
|
||||
getLogger().log(Level.SEVERE, "Error saving default " + fileName, ioExc);
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BungeeCore getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setAuthPluginHook(BungeeAuthPlugin authPlugin) {
|
||||
this.bungeeAuthPlugin = authPlugin;
|
||||
core.setAuthPluginHook(authPlugin);
|
||||
}
|
||||
|
||||
public Configuration getConfig() {
|
||||
return configuration;
|
||||
return config;
|
||||
}
|
||||
|
||||
public ConcurrentMap<PendingConnection, BungeeLoginSession> getSession() {
|
||||
@@ -106,14 +115,15 @@ public class FastLoginBungee extends Plugin {
|
||||
*
|
||||
* @return the auth hook for BungeeCord. null if none found
|
||||
*/
|
||||
@Deprecated
|
||||
public BungeeAuthPlugin getBungeeAuthPlugin() {
|
||||
return bungeeAuthPlugin;
|
||||
return (BungeeAuthPlugin) core.getAuthPluginHook();
|
||||
}
|
||||
|
||||
private void registerHook() {
|
||||
Plugin plugin = getProxy().getPluginManager().getPlugin("BungeeAuth");
|
||||
if (plugin != null) {
|
||||
bungeeAuthPlugin = new BungeeAuthHook();
|
||||
core.setAuthPluginHook(new BungeeAuthHook());
|
||||
getLogger().info("Hooked into BungeeAuth");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class ImportCommand extends Command {
|
||||
|
||||
private final BungeeCore core;
|
||||
|
||||
public ImportCommand(FastLoginBungee plugin) {
|
||||
super("import-db", plugin.getDescription().getName().toLowerCase() + ".import");
|
||||
|
||||
this.core = plugin.getCore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length < 2) {
|
||||
String message = ChatColor.DARK_RED + "You need to specify the import plugin and database type";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
ImportPlugin importPlugin;
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "autoin":
|
||||
importPlugin = ImportPlugin.AUTO_IN;
|
||||
break;
|
||||
case "bpa":
|
||||
importPlugin = ImportPlugin.BPA;
|
||||
break;
|
||||
case "eldzi":
|
||||
importPlugin = ImportPlugin.ELDZI;
|
||||
break;
|
||||
default:
|
||||
String message = ChatColor.DARK_RED + "Unknown auto login plugin";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean sqlite;
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "sqlite":
|
||||
sqlite = true;
|
||||
break;
|
||||
case "mysql":
|
||||
sqlite = false;
|
||||
break;
|
||||
default:
|
||||
String message = ChatColor.DARK_RED + "Unknown storage type to import from. Either SQLite or MySQL";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
String host = "";
|
||||
String database = "";
|
||||
String username = "";
|
||||
String password = "";
|
||||
if (!sqlite) {
|
||||
if (args.length <= 5) {
|
||||
String message = ChatColor.DARK_RED + "If importing from MySQL, you need to specify host database "
|
||||
+ "and username passowrd too";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
host = args[2];
|
||||
database = args[3];
|
||||
username = args[4];
|
||||
password = args[5];
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
boolean success = core.importDatabase(importPlugin, true, storage, host, database, username, password);
|
||||
if (success) {
|
||||
sender.sendMessage(convertFromLegacy(ChatColor.DARK_GREEN + "Successful imported the data"));
|
||||
} else {
|
||||
sender.sendMessage(convertFromLegacy(ChatColor.DARK_RED + "Failed to import the data. Check out the logs"));
|
||||
}
|
||||
}
|
||||
|
||||
private BaseComponent[] convertFromLegacy(String message) {
|
||||
return TextComponent.fromLegacyText(message);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,40 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.MojangApiConnector;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
|
||||
public class MojangApiBungee extends MojangApiConnector {
|
||||
|
||||
public MojangApiBungee(FastLoginCore plugin) {
|
||||
super(plugin);
|
||||
public MojangApiBungee(Logger logger, List<String> localAddresses, int rateLimit) {
|
||||
super(logger, localAddresses, rateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getUUIDFromJson(String json) {
|
||||
MojangPlayer mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer.class);
|
||||
return FastLoginCore.parseId(mojangPlayer.getId());
|
||||
protected String getUUIDFromJson(String json) {
|
||||
boolean isArray = json.startsWith("[");
|
||||
|
||||
MojangPlayer mojangPlayer;
|
||||
if (isArray) {
|
||||
mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer[].class)[0];
|
||||
} else {
|
||||
mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer.class);
|
||||
}
|
||||
|
||||
String id = mojangPlayer.getId();
|
||||
if ("null".equals(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinedServer(Object session, String serverId) {
|
||||
public boolean hasJoinedServer(LoginSession session, String serverId) {
|
||||
//this is not needed in Bungee
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.github.games647.fastlogin.bungee.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import me.vik1395.BungeeAuth.ListenerClass;
|
||||
import me.vik1395.BungeeAuth.Main;
|
||||
@@ -19,29 +22,34 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
*
|
||||
* Spigot: https://www.spigotmc.org/resources/bungeeauth.493/
|
||||
*/
|
||||
public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
public class BungeeAuthHook implements AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L32
|
||||
private final Tables databaseConnection = new Tables();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(ProxiedPlayer player) {
|
||||
String playerName = player.getName();
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L92-95
|
||||
Main.plonline.add(player.getName());
|
||||
if (Main.plonline.contains(playerName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Main.plonline.add(playerName);
|
||||
|
||||
//renamed from ct to databaseConnection
|
||||
// databaseConnection.setStatus(player.getName(), "online");
|
||||
Class<?>[] parameterTypes = new Class<?>[]{String.class, String.class};
|
||||
Object[] arguments = new Object[]{player.getName(), "online"};
|
||||
Object[] arguments = new Object[]{playerName, "online"};
|
||||
|
||||
try {
|
||||
callProtected("setStatus", parameterTypes, arguments);
|
||||
ListenerClass.movePlayer(player, false);
|
||||
|
||||
//proparly not thread-safe
|
||||
ListenerClass.prelogin.get(player.getName()).cancel();
|
||||
ListenerClass.prelogin.get(playerName).cancel();
|
||||
} catch (Exception ex) {
|
||||
Main.plugin.getLogger().severe("[BungeeAuth] Error force loging in player");
|
||||
Main.plugin.getLogger().log(Level.SEVERE, "Error force loging in player", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,20 +81,17 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
String hash = ph.newHash(Pw, pType);
|
||||
|
||||
//creates a new SQL entry with the player's details.
|
||||
|
||||
//renamed t to databaseConnection
|
||||
// databaseConnection.newPlayerEntry(player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen);
|
||||
|
||||
Class<?>[] parameterTypes = new Class<?>[] {String.class, String.class, String.class, String.class
|
||||
, String.class, String.class, String.class, String.class};
|
||||
Object[] arguments = new Object[] {player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen};
|
||||
|
||||
try {
|
||||
|
||||
callProtected("newPlayerEntry", parameterTypes, arguments);
|
||||
//proparly not thread-safe
|
||||
forceLogin(player);
|
||||
} catch (Exception ex) {
|
||||
Main.plugin.getLogger().severe("[BungeeAuth] Error when creating a new player in the Database");
|
||||
Main.plugin.getLogger().log(Level.SEVERE, "[BungeeAuth] Error when creating a new player in the Database", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,10 +100,12 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
|
||||
//pail ;(
|
||||
private void callProtected(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
|
||||
Class<? extends Tables> tableClass = databaseConnection.getClass();
|
||||
Class<Tables> tableClass = Tables.class;
|
||||
|
||||
Method method = tableClass.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
//renamed t to databaseConnection
|
||||
//databaseConnection.newPlayerEntry(player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen);
|
||||
method.invoke(databaseConnection, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,20 @@
|
||||
package com.github.games647.fastlogin.bungee.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* Represents a supporting authentication plugin in BungeeCord/Waterfall/... servers
|
||||
* @deprecated please use com.github.games647.fastlogin.core.hooks.AuthPlugin<net.md_5.bungee.api.connection.ProxiedPlayer>
|
||||
*/
|
||||
public interface BungeeAuthPlugin {
|
||||
@Deprecated
|
||||
public interface BungeeAuthPlugin extends AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
/**
|
||||
* Login the premium (paid account) player after
|
||||
* the player joined successfully a server.
|
||||
*
|
||||
* @param player the player that needs to be logged in
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
@Override
|
||||
boolean forceLogin(ProxiedPlayer player);
|
||||
|
||||
/**
|
||||
* Checks whether an account exists for this player name.
|
||||
*
|
||||
* This check should check if a cracked player account exists
|
||||
* so we can be sure the premium player doesn't steal the account
|
||||
* of that player.
|
||||
*
|
||||
* This operation will be performed async while the player is
|
||||
* connecting
|
||||
*
|
||||
* @param playerName player name
|
||||
* @return if the player has an account
|
||||
* @throws Exception if an error occurred
|
||||
*/
|
||||
@Override
|
||||
boolean isRegistered(String playerName) throws Exception;
|
||||
|
||||
/**
|
||||
* Forces a register in order to protect the paid account.
|
||||
* The method will be invoked after the player joined a server.
|
||||
*
|
||||
* After a successful registration the player should be logged
|
||||
* in too.
|
||||
*
|
||||
* The method will be called only for premium accounts.
|
||||
* So it's recommended to set additionally premium property
|
||||
* if possible.
|
||||
*
|
||||
* If we don't register an account, cracked players
|
||||
* could steal the unregistered account from the paid
|
||||
* player account
|
||||
*
|
||||
* @param player the premium account
|
||||
* @param password a strong random generated password
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
@Override
|
||||
boolean forceRegister(ProxiedPlayer player, String password);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.tasks.AsyncPremiumCheck;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.tasks.AsyncPremiumCheck;
|
||||
import com.github.games647.fastlogin.bungee.tasks.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -14,8 +14,8 @@ import java.util.logging.Level;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.LoginEvent;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
@@ -23,6 +23,7 @@ import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.connection.LoginResult.Property;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
|
||||
/**
|
||||
* Enables online mode logins for specified users and sends
|
||||
@@ -31,7 +32,7 @@ import net.md_5.bungee.event.EventHandler;
|
||||
*/
|
||||
public class PlayerConnectionListener implements Listener {
|
||||
|
||||
protected final FastLoginBungee plugin;
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public PlayerConnectionListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -44,18 +45,31 @@ public class PlayerConnectionListener implements Listener {
|
||||
}
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new AsyncPremiumCheck(plugin, preLoginEvent));
|
||||
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
AsyncPremiumCheck asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, asyncPremiumCheck);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLogin(PostLoginEvent loginEvent) {
|
||||
ProxiedPlayer player = loginEvent.getPlayer();
|
||||
PendingConnection connection = player.getPendingConnection();
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onLogin(LoginEvent loginEvent) {
|
||||
if (loginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//use the loginevent instead of the postlogin event in order to send the loginsuccess packet to the client
|
||||
//with the offline uuid this makes it possible to set the skin then
|
||||
PendingConnection connection = loginEvent.getConnection();
|
||||
String username = connection.getName();
|
||||
if (connection.isOnlineMode()) {
|
||||
String ip = connection.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().remove(ip + username);
|
||||
|
||||
LoginSession session = plugin.getSession().get(connection);
|
||||
session.setUuid(connection.getUniqueId());
|
||||
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
playerProfile.setUuid(player.getUniqueId());
|
||||
playerProfile.setUuid(connection.getUniqueId());
|
||||
|
||||
//bungeecord will do this automatically so override it on disabled option
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
@@ -65,7 +79,7 @@ public class PlayerConnectionListener implements Listener {
|
||||
|
||||
//bungeecord doesn't support overriding the premium uuid
|
||||
//so we have to do it with reflection
|
||||
Field idField = initialHandler.getClass().getDeclaredField("uniqueId");
|
||||
Field idField = InitialHandler.class.getDeclaredField("uniqueId");
|
||||
idField.setAccessible(true);
|
||||
idField.set(connection, offlineUUID);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
@@ -91,8 +105,9 @@ public class PlayerConnectionListener implements Listener {
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(PlayerDisconnectEvent disconnectEvent) {
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
@@ -16,7 +19,7 @@ import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PluginMessageListener implements Listener {
|
||||
|
||||
protected final FastLoginBungee plugin;
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public PluginMessageListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -35,40 +38,53 @@ public class PluginMessageListener implements Listener {
|
||||
|
||||
//check if the message is sent from the server
|
||||
if (Server.class.isAssignableFrom(pluginMessageEvent.getSender().getClass())) {
|
||||
readMessage(pluginMessageEvent);
|
||||
//so that we can safely process this in the background
|
||||
byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
|
||||
ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
|
||||
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> readMessage(forPlayer, data));
|
||||
}
|
||||
}
|
||||
|
||||
private void readMessage(PluginMessageEvent pluginMessageEvent) {
|
||||
byte[] data = pluginMessageEvent.getData();
|
||||
private void readMessage(ProxiedPlayer forPlayer, byte[] data) {
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
String subchannel = dataInput.readUTF();
|
||||
|
||||
ProxiedPlayer fromPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
|
||||
if ("ON".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, playerName, true);
|
||||
if (playerName.equals(forPlayer.getName()) && plugin.getConfig().getBoolean("premium-warning")
|
||||
&& !plugin.getCore().getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = plugin.getCore().getMessage("premium-warning");
|
||||
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
|
||||
plugin.getCore().getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin.getCore(), forPlayer, playerName, true);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("OFF".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, playerName, false);
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin.getCore(), forPlayer, playerName, false);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("SUCCESS".equals(subchannel)) {
|
||||
if (fromPlayer.getPendingConnection().isOnlineMode()) {
|
||||
//bukkit module successfully received and force logged in the user
|
||||
//update only on success to prevent corrupt data
|
||||
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(fromPlayer.getName());
|
||||
BungeeLoginSession loginSession = plugin.getSession().get(fromPlayer.getPendingConnection());
|
||||
loginSession.setRegistered(true);
|
||||
onSuccessMessage(forPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
playerProfile.setPremium(true);
|
||||
//we override this in the loginevent
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
loginSession.setAlreadySaved(true);
|
||||
}
|
||||
private void onSuccessMessage(ProxiedPlayer forPlayer) {
|
||||
if (forPlayer.getPendingConnection().isOnlineMode()) {
|
||||
//bukkit module successfully received and force logged in the user
|
||||
//update only on success to prevent corrupt data
|
||||
BungeeLoginSession loginSession = plugin.getSession().get(forPlayer.getPendingConnection());
|
||||
PlayerProfile playerProfile = loginSession.getProfile();
|
||||
loginSession.setRegistered(true);
|
||||
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
loginSession.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,53 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSource;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.AsyncEvent;
|
||||
|
||||
public class AsyncPremiumCheck implements Runnable {
|
||||
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, BungeeLoginSource> implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final PreLoginEvent preLoginEvent;
|
||||
private final AsyncEvent<?> preLoginEvent;
|
||||
|
||||
private final PendingConnection connection;
|
||||
|
||||
public AsyncPremiumCheck(FastLoginBungee plugin, AsyncEvent<?> preLoginEvent, PendingConnection connection) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent) {
|
||||
this.plugin = plugin;
|
||||
this.preLoginEvent = preLoginEvent;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
plugin.getSession().remove(connection);
|
||||
|
||||
String username = connection.getName();
|
||||
try {
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
||||
if (profile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile.getUserId() == -1) {
|
||||
UUID premiumUUID = null;
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
if (premiumUUID == null
|
||||
|| checkNameChange(premiumUUID, connection, username)
|
||||
|| checkPremiumName(username, connection, profile)) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
plugin.getSession().put(connection, new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
requestPremiumLogin(connection, profile, username, true);
|
||||
} else {
|
||||
plugin.getSession().put(connection, new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to check premium state", ex);
|
||||
super.onLogin(username, new BungeeLoginSource(connection));
|
||||
} finally {
|
||||
preLoginEvent.completeIntent(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPremiumName(String username, PendingConnection connection, PlayerProfile profile)
|
||||
throws Exception {
|
||||
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
|
||||
if (plugin.getConfig().getBoolean("autoRegister")
|
||||
&& (authPlugin == null || !authPlugin.isRegistered(username))) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
requestPremiumLogin(connection, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public void requestPremiumLogin(BungeeLoginSource source, PlayerProfile profile, String username, boolean registered) {
|
||||
source.setOnlineMode();
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, registered, profile));
|
||||
|
||||
return false;
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().put(ip + username, new Object());
|
||||
}
|
||||
|
||||
private boolean checkNameChange(UUID premiumUUID, PendingConnection connection, String username) {
|
||||
//user not exists in the db
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
//uuid exists in the database
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
requestPremiumLogin(connection, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void requestPremiumLogin(PendingConnection con, PlayerProfile profile, String username, boolean register) {
|
||||
con.setOnlineMode(true);
|
||||
plugin.getSession().put(con, new BungeeLoginSession(username, register, profile));
|
||||
@Override
|
||||
public void startCrackedSession(BungeeLoginSource source, PlayerProfile profile, String username) {
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.BungeeCore;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
@@ -8,14 +8,13 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public class AsyncToggleMessage implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final BungeeCore core;
|
||||
private final ProxiedPlayer fromPlayer;
|
||||
private final String targetPlayer;
|
||||
private final boolean toPremium;
|
||||
|
||||
public AsyncToggleMessage(FastLoginBungee plugin, ProxiedPlayer fromPlayer, String targetPlayer
|
||||
, boolean toPremium) {
|
||||
this.plugin = plugin;
|
||||
public AsyncToggleMessage(BungeeCore core, ProxiedPlayer fromPlayer, String targetPlayer, boolean toPremium) {
|
||||
this.core = core;
|
||||
this.fromPlayer = fromPlayer;
|
||||
this.targetPlayer = targetPlayer;
|
||||
this.toPremium = toPremium;
|
||||
@@ -31,27 +30,28 @@ public class AsyncToggleMessage implements Runnable {
|
||||
}
|
||||
|
||||
private void turnOffPremium() {
|
||||
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(targetPlayer);
|
||||
if (!playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("not-premium")));
|
||||
PlayerProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
|
||||
//existing player is already cracked
|
||||
if (playerProfile.getUserId() != -1 && !playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("not-premium")));
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(false);
|
||||
playerProfile.setUuid(null);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("remove-premium")));
|
||||
core.getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("remove-premium")));
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(targetPlayer);
|
||||
PlayerProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
|
||||
if (playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("already-exists")));
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("already-exists")));
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("add-premium")));
|
||||
core.getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("add-premium")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
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;
|
||||
@@ -27,38 +29,84 @@ public class ForceLoginTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PendingConnection pendingConnection = player.getPendingConnection();
|
||||
BungeeLoginSession session = plugin.getSession().get(pendingConnection);
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
try {
|
||||
PendingConnection pendingConnection = player.getPendingConnection();
|
||||
BungeeLoginSession session = plugin.getSession().get(pendingConnection);
|
||||
|
||||
if (player.isConnected()) {
|
||||
if (session == null || !player.isConnected()) {
|
||||
plugin.getLogger().log(Level.FINE, "Invalid session player {0} propaly left the server", player);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
//force login only on success
|
||||
if (pendingConnection.isOnlineMode()) {
|
||||
boolean autoRegister = session.needsRegistration();
|
||||
|
||||
//2fa authentication - no need to send bukkit force login notification and so we also don't need
|
||||
// to wait for a response -> save immediatly
|
||||
if (!plugin.getConfig().getBoolean("autoLogin")) {
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
session.setAlreadySaved(true);
|
||||
}
|
||||
|
||||
AuthPlugin<ProxiedPlayer> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
} else if (session.needsRegistration()) {
|
||||
forceRegister(session, authPlugin);
|
||||
} else if (authPlugin.forceLogin(player)) {
|
||||
forceLogin(session, authPlugin);
|
||||
}
|
||||
} else {
|
||||
//cracked player
|
||||
if (!session.isAlreadySaved()) {
|
||||
playerProfile.setPremium(false);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
session.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.INFO, "ERROR ON FORCE LOGIN", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void forceRegister(BungeeLoginSession session, AuthPlugin<ProxiedPlayer> authPlugin) {
|
||||
if (session.isAlreadyLogged()) {
|
||||
sendBukkitLoginNotification(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//force login only on success
|
||||
if (pendingConnection.isOnlineMode()) {
|
||||
boolean autoRegister = session.needsRegistration();
|
||||
session.setAlreadyLogged(true);
|
||||
|
||||
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
} else if (session.needsRegistration()) {
|
||||
String password = plugin.generateStringPassword();
|
||||
if (authPlugin.forceRegister(player, password)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
}
|
||||
} else if (authPlugin.forceLogin(player)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
String password = plugin.getCore().getPasswordGenerator().getRandomPassword(player);
|
||||
if (authPlugin.forceRegister(player, password)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(true);
|
||||
String message = plugin.getCore().getMessage("auto-register");
|
||||
if (message != null) {
|
||||
message = message.replace("%password", password);
|
||||
player.sendMessage(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
} else {
|
||||
//cracked player
|
||||
if (!session.isAlreadySaved()) {
|
||||
playerProfile.setPremium(false);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
session.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void forceLogin(BungeeLoginSession session, AuthPlugin<ProxiedPlayer> authPlugin) {
|
||||
if (session.isAlreadyLogged()) {
|
||||
sendBukkitLoginNotification(false);
|
||||
return;
|
||||
}
|
||||
|
||||
session.setAlreadyLogged(true);
|
||||
if (authPlugin.forceLogin(player)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(false);
|
||||
String message = plugin.getCore().getMessage("auto-login");
|
||||
if (message != null) {
|
||||
player.sendMessage(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ main: ${project.groupId}.${project.artifactId}.${project.name}
|
||||
version: ${project.version}
|
||||
author: games647, http://github.com/games647/FastLogin/graphs/contributors
|
||||
|
||||
softdepends:
|
||||
softdepend:
|
||||
# BungeeCord auth plugins
|
||||
- BungeeAuth
|
||||
|
||||
|
||||
11
core/pom.xml
11
core/pom.xml
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>2.4.6</version>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
@@ -28,5 +28,12 @@
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>1.7.21</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>10.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
@@ -8,17 +9,22 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Storage {
|
||||
public class AuthStorage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private final FastLoginCore core;
|
||||
private final FastLoginCore<?> core;
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
public Storage(FastLoginCore core, String driver, String host, int port, String databasePath
|
||||
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
|
||||
private final Calendar calendar = Calendar.getInstance(Locale.US);
|
||||
|
||||
public AuthStorage(FastLoginCore<?> core, String driver, String host, int port, String databasePath
|
||||
, String user, String pass) {
|
||||
this.core = core;
|
||||
|
||||
@@ -30,8 +36,6 @@ public class Storage {
|
||||
|
||||
databasePath = databasePath.replace("{pluginDir}", core.getDataFolder().getAbsolutePath());
|
||||
|
||||
databaseConfig.setThreadFactory(core.getThreadFactory());
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (driver.contains("sqlite")) {
|
||||
jdbcUrl += "sqlite" + "://" + databasePath;
|
||||
@@ -44,12 +48,17 @@ public class Storage {
|
||||
this.dataSource = new HikariDataSource(databaseConfig);
|
||||
}
|
||||
|
||||
public HikariDataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public void createTables() throws SQLException {
|
||||
Connection con = null;
|
||||
Statement createStmt = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
createStmt = con.createStatement();
|
||||
|
||||
String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " ("
|
||||
+ "UserID INTEGER PRIMARY KEY AUTO_INCREMENT, "
|
||||
+ "UUID CHAR(36), "
|
||||
@@ -57,7 +66,6 @@ public class Storage {
|
||||
+ "Premium BOOLEAN NOT NULL, "
|
||||
+ "LastIp VARCHAR(255) NOT NULL, "
|
||||
+ "LastLogin TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
||||
+ "UNIQUE (UUID), "
|
||||
//the premium shouldn't steal the cracked account by changing the name
|
||||
+ "UNIQUE (Name) "
|
||||
+ ")";
|
||||
@@ -67,6 +75,34 @@ public class Storage {
|
||||
}
|
||||
|
||||
createStmt.executeUpdate(createDataStmt);
|
||||
|
||||
//drop the old unique uuid index
|
||||
try {
|
||||
if (dataSource.getJdbcUrl().contains("sqlite")) {
|
||||
String tempTableCreate = createDataStmt.replace(PREMIUM_TABLE, PREMIUM_TABLE + "_TEMP")
|
||||
//if we already imported the table fail here
|
||||
.replace("IF NOT EXISTS", "");
|
||||
//create a temp table insert it there and then back
|
||||
createStmt.executeUpdate(tempTableCreate);
|
||||
createStmt.executeUpdate("INSERT INTO " + PREMIUM_TABLE + "_TEMP SELECT * FROM " + PREMIUM_TABLE);
|
||||
|
||||
createStmt.executeUpdate("DROP TABLE " + PREMIUM_TABLE);
|
||||
createStmt.executeUpdate(createDataStmt);
|
||||
|
||||
//insert it back into the new table
|
||||
createStmt.executeUpdate("INSERT INTO " + PREMIUM_TABLE + " SELECT * FROM " + PREMIUM_TABLE + "_TEMP");
|
||||
} else {
|
||||
createStmt.executeUpdate("ALTER TABLE premium DROP INDEX UUID");
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
//silent - we already migrated
|
||||
}
|
||||
|
||||
try {
|
||||
createStmt.executeUpdate("CREATE INDEX uuid_idx on premium (UUID)");
|
||||
} catch (SQLException sqlEx) {
|
||||
//silent - we already migrated
|
||||
}
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(createStmt);
|
||||
@@ -79,24 +115,18 @@ public class Storage {
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=?");
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=? LIMIT 1");
|
||||
loadStmt.setString(1, name);
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
String unparsedUUID = resultSet.getString(2);
|
||||
UUID uuid;
|
||||
if (unparsedUUID == null) {
|
||||
uuid = null;
|
||||
} else {
|
||||
uuid = FastLoginCore.parseId(unparsedUUID);
|
||||
}
|
||||
UUID uuid = FastLoginCore.parseId(resultSet.getString(2));
|
||||
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6).getTime();
|
||||
long lastLogin = resultSet.getTimestamp(6, calendar).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
return playerProfile;
|
||||
} else {
|
||||
@@ -120,7 +150,7 @@ public class Storage {
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=?");
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=? LIMIT 1");
|
||||
loadStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
@@ -130,7 +160,7 @@ public class Storage {
|
||||
String name = resultSet.getString(3);
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6).getTime();
|
||||
long lastLogin = resultSet.getTimestamp(6, calendar).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
return playerProfile;
|
||||
}
|
||||
@@ -155,28 +185,10 @@ public class Storage {
|
||||
con = dataSource.getConnection();
|
||||
|
||||
UUID uuid = playerProfile.getUuid();
|
||||
|
||||
if (playerProfile.getUserId() == -1) {
|
||||
//User was authenticated with a premium authentication, so it's possible that the player is premium
|
||||
if (uuid != null) {
|
||||
updateStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE
|
||||
+ " SET NAME=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP"
|
||||
+ " WHERE UUID=? AND PREMIUM=1");
|
||||
|
||||
updateStmt.setString(1, playerProfile.getPlayerName());
|
||||
updateStmt.setString(2, playerProfile.getLastIp());
|
||||
updateStmt.setString(3, uuid.toString().replace("-", ""));
|
||||
|
||||
int affectedRows = updateStmt.executeUpdate();
|
||||
if (affectedRows > 0) {
|
||||
//username changed and we updated the existing database record
|
||||
//so we don't need to run an insert
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
saveStmt = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) "
|
||||
, Statement.RETURN_GENERATED_KEYS);
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) ", Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
if (uuid == null) {
|
||||
saveStmt.setString(1, null);
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class BalancedSSLFactory extends SSLSocketFactory {
|
||||
|
||||
private final SSLSocketFactory oldFactory;
|
||||
|
||||
//in order to be thread-safe
|
||||
private final List<InetAddress> localAddresses;
|
||||
|
||||
private AtomicInteger id;
|
||||
|
||||
public BalancedSSLFactory(SSLSocketFactory oldFactory, Iterable<InetAddress> localAddresses) {
|
||||
this.oldFactory = oldFactory;
|
||||
this.localAddresses = ImmutableList.copyOf(localAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return oldFactory.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return oldFactory.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket socket, String host, int port, boolean autoclose) throws IOException {
|
||||
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort)
|
||||
throws IOException, UnknownHostException {
|
||||
//default
|
||||
return oldFactory.createSocket(host, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port, InetAddress local, int localPort) throws IOException {
|
||||
//Default
|
||||
return oldFactory.createSocket(host, port, local, localPort);
|
||||
}
|
||||
|
||||
private InetAddress getNextLocalAddress() {
|
||||
int index = id.incrementAndGet() % localAddresses.size();
|
||||
return localAddresses.get(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
|
||||
/**
|
||||
* Represents a Guava CacheBuilder that is compatible with both Guava 10 (Minecraft 1.7.X) and 13
|
||||
*/
|
||||
public class CompatibleCacheBuilder<K, V> {
|
||||
|
||||
private static Method BUILD_METHOD;
|
||||
private static Method AS_MAP_METHOD;
|
||||
|
||||
/**
|
||||
* Construct a new safe cache builder.
|
||||
*
|
||||
* @param <K> Key type
|
||||
* @param <V> Value type
|
||||
*
|
||||
* @return A new cache builder.
|
||||
*/
|
||||
public static <K, V> CompatibleCacheBuilder<K, V> newBuilder() {
|
||||
return new CompatibleCacheBuilder<>();
|
||||
}
|
||||
|
||||
private final CacheBuilder<K, V> builder;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private CompatibleCacheBuilder() {
|
||||
builder = (CacheBuilder<K, V>) CacheBuilder.newBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The table is
|
||||
* internally partitioned to try to permit the indicated number of concurrent updates without contention. Because
|
||||
* assignment of entries to these partitions is not necessarily uniform, the actual concurrency observed may vary.
|
||||
* Ideally, you should choose a value to accommodate as many threads as will ever concurrently modify the table.
|
||||
* Using a significantly higher value than you need can waste space and time, and a significantly lower value can
|
||||
* lead to thread contention. But overestimates and underestimates within an order of magnitude do not usually have
|
||||
* much noticeable impact. A value of one permits only one thread to modify the cache at a time, but since read
|
||||
* operations can proceed concurrently, this still yields higher concurrency than full synchronization. Defaults to
|
||||
* 4.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b>The default may change in the future. If you care about this value, you should always choose it
|
||||
* explicitly.
|
||||
*
|
||||
* @param concurrencyLevel New concurrency level
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive
|
||||
* @throws IllegalStateException if a concurrency level was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
|
||||
builder.concurrencyLevel(concurrencyLevel);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after
|
||||
* the entry's creation, or last access. Access time is reset by
|
||||
* {@link com.google.common.cache.Cache#get Cache.get()}, but not by operations on the view returned by
|
||||
* {@link com.google.common.cache.Cache#asMap() Cache.asMap()}.
|
||||
*
|
||||
* <p>
|
||||
* When {@code duration} is zero, elements will be evicted immediately after being loaded into the cache. This has
|
||||
* the same effect as invoking {@link #maximumSize maximumSize}{@code (0)}. It can be useful in testing, or to
|
||||
* disable caching temporarily without a code change.
|
||||
*
|
||||
* <p>
|
||||
* Expired entries may be counted by {@link com.google.common.cache.Cache#size Cache.size()}, but will never be
|
||||
* visible to read or write operations. Expired entries are currently cleaned up during write operations, or during
|
||||
* occasional read operations in the absense of writes; though this behavior may change in the future.
|
||||
*
|
||||
* @param duration the length of time after an entry is last accessed that it should be automatically removed
|
||||
* @param unit the unit that {@code duration} is expressed in
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code duration} is negative
|
||||
* @throws IllegalStateException if the time to idle or time to live was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) {
|
||||
builder.expireAfterAccess(duration, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after
|
||||
* the entry's creation, or the most recent replacement of its value.
|
||||
*
|
||||
* <p>
|
||||
* When {@code duration} is zero, elements will be evicted immediately after being loaded into the cache. This has
|
||||
* the same effect as invoking {@link #maximumSize maximumSize}{@code (0)}. It can be useful in testing, or to
|
||||
* disable caching temporarily without a code change.
|
||||
*
|
||||
* <p>
|
||||
* Expired entries may be counted by {@link com.google.common.cache.Cache#size Cache.size()}, but will never be
|
||||
* visible to read or write operations. Expired entries are currently cleaned up during write operations, or during
|
||||
* occasional read operations in the absense of writes; though this behavior may change in the future.
|
||||
*
|
||||
* @param duration the length of time after an entry is created that it should be automatically removed
|
||||
* @param unit the unit that {@code duration} is expressed in
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code duration} is negative
|
||||
* @throws IllegalStateException if the time to live or time to idle was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
|
||||
builder.expireAfterWrite(duration, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum total size for the internal hash tables. For example, if the initial capacity is {@code 60}, and
|
||||
* the concurrency level is {@code 8}, then eight segments are created, each having a hash table of size eight.
|
||||
* Providing a large enough estimate at construction time avoids the need for expensive resizing operations later,
|
||||
* but setting this value unnecessarily high wastes memory.
|
||||
*
|
||||
* @param initialCapacity - initial capacity
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code initialCapacity} is negative
|
||||
* @throws IllegalStateException if an initial capacity was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> initialCapacity(int initialCapacity) {
|
||||
builder.initialCapacity(initialCapacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum number of entries the cache may contain. Note that the cache <b>may evict an entry before
|
||||
* this limit is exceeded</b>. As the cache size grows close to the maximum, the cache evicts entries that are less
|
||||
* likely to be used again. For example, the cache may evict an entry because it hasn't been used recently or very
|
||||
* often.
|
||||
*
|
||||
* <p>
|
||||
* When {@code size} is zero, elements will be evicted immediately after being loaded into the cache. This has the
|
||||
* same effect as invoking {@link #expireAfterWrite expireAfterWrite}{@code (0, unit)} or {@link #expireAfterAccess expireAfterAccess}{@code (0,
|
||||
* unit)}. It can be useful in testing, or to disable caching temporarily without a code change.
|
||||
*
|
||||
* @param size the maximum size of the cache
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code size} is negative
|
||||
* @throws IllegalStateException if a maximum size was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> maximumSize(int size) {
|
||||
builder.maximumSize(size);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a listener instance, which all caches built using this {@code CacheBuilder} will notify each time an
|
||||
* entry is removed from the cache by any means.
|
||||
*
|
||||
* <p>
|
||||
* Each cache built by this {@code CacheBuilder} after this method is called invokes the supplied listener after
|
||||
* removing an element for any reason (see removal causes in
|
||||
* {@link com.google.common.cache.RemovalCause RemovalCause}). It will invoke the listener during invocations of any
|
||||
* of that cache's public methods (even read-only methods).
|
||||
*
|
||||
* <p>
|
||||
* <b>Important note:</b> Instead of returning <em>this</em> as a {@code CacheBuilder} instance, this method returns
|
||||
* {@code CacheBuilder<K1, V1>}. From this point on, either the original reference or the returned reference may be
|
||||
* used to complete configuration and build the cache, but only the "generic" one is type-safe. That is, it will
|
||||
* properly prevent you from building caches whose key or value types are incompatible with the types accepted by
|
||||
* the listener already provided; the {@code CacheBuilder} type cannot do this. For best results, simply use the
|
||||
* standard method-chaining idiom, as illustrated in the documentation at top, configuring a {@code CacheBuilder}
|
||||
* and building your {@link com.google.common.cache.Cache Cache} all in a single statement.
|
||||
*
|
||||
* <p>
|
||||
* <b>Warning:</b> if you ignore the above advice, and use this {@code CacheBuilder} to build a cache whose key or
|
||||
* value type is incompatible with the listener, you will likely experience a {@link ClassCastException} at some
|
||||
* <i>undefined</i> point in the future.
|
||||
*
|
||||
* @param <K1> Key type
|
||||
* @param <V1> Value type
|
||||
* @param listener - removal listener
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if a removal listener was already set
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K1 extends K, V1 extends V> CompatibleCacheBuilder<K1, V1> removalListener(RemovalListener<? super K1, ? super V1> listener) {
|
||||
builder.removalListener(listener);
|
||||
return (CompatibleCacheBuilder<K1, V1>) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a nanosecond-precision time source for use in determining when entries should be expired. By default,
|
||||
* {@link System#nanoTime} is used.
|
||||
*
|
||||
* <p>
|
||||
* The primary intent of this method is to facilitate testing of caches which have been configured with
|
||||
* {@link #expireAfterWrite} or {@link #expireAfterAccess}.
|
||||
*
|
||||
* @param ticker - ticker
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if a ticker was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> ticker(Ticker ticker) {
|
||||
builder.ticker(ticker);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each value (not key) stored in the cache should be wrapped in a
|
||||
* {@link java.lang.ref.SoftReference SoftReference} (by default, strong references are used). Softly-referenced
|
||||
* objects will be garbage-collected in a <i>globally</i>
|
||||
* least-recently-used manner, in response to memory demand.
|
||||
*
|
||||
* <p>
|
||||
* <b>Warning:</b> in most circumstances it is better to set a per-cache {@linkplain #maximumSize maximum size}
|
||||
* instead of using soft references. You should only use this method if you are well familiar with the practical
|
||||
* consequences of soft references.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b> when this method is used, the resulting cache will use identity ({@code ==}) comparison to determine
|
||||
* equality of values.
|
||||
*
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if the value strength was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> softValues() {
|
||||
builder.softValues();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each key (not value) stored in the cache should be wrapped in a
|
||||
* {@link java.lang.ref.WeakReference WeakReference} (by default, strong references are used).
|
||||
*
|
||||
* <p>
|
||||
* <b>Warning:</b> when this method is used, the resulting cache will use identity ({@code ==}) comparison to
|
||||
* determine equality of keys.
|
||||
*
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if the key strength was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> weakKeys() {
|
||||
builder.weakKeys();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each value (not key) stored in the cache should be wrapped in a
|
||||
* {@link java.lang.ref.WeakReference WeakReference} (by default, strong references are used).
|
||||
*
|
||||
* <p>
|
||||
* Weak values will be garbage collected once they are weakly reachable. This makes them a poor candidate for
|
||||
* caching; consider {@link #softValues} instead.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b> when this method is used, the resulting cache will use identity ({@code ==}) comparison to determine
|
||||
* equality of values.
|
||||
*
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if the value strength was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> weakValues() {
|
||||
builder.weakValues();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache wrapped as a ConcurrentMap.
|
||||
* <p>
|
||||
* We can't return the direct Cache instance as it changed in Guava 13.
|
||||
*
|
||||
* @param <K1> Key type
|
||||
* @param <V1> Value type
|
||||
* @param loader - cache loader
|
||||
* @return The cache as a a map.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K1 extends K, V1 extends V> ConcurrentMap<K1, V1> build(CacheLoader<? super K1, V1> loader) {
|
||||
Object cache = null;
|
||||
|
||||
if (BUILD_METHOD == null) {
|
||||
try {
|
||||
BUILD_METHOD = builder.getClass().getDeclaredMethod("build", CacheLoader.class);
|
||||
BUILD_METHOD.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to find CacheBuilder.build(CacheLoader)", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to build the Cache
|
||||
try {
|
||||
cache = BUILD_METHOD.invoke(builder, loader);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to invoke " + BUILD_METHOD + " on " + builder, e);
|
||||
}
|
||||
|
||||
if (AS_MAP_METHOD == null) {
|
||||
try {
|
||||
AS_MAP_METHOD = cache.getClass().getMethod("asMap");
|
||||
AS_MAP_METHOD.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to find Cache.asMap() in " + cache, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve it as a map
|
||||
try {
|
||||
return (ConcurrentMap<K1, V1>) AS_MAP_METHOD.invoke(cache);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to invoke " + AS_MAP_METHOD + " on " + cache, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public abstract class FastLoginCore {
|
||||
|
||||
public static UUID parseId(String withoutDashes) {
|
||||
return UUID.fromString(withoutDashes.substring(0, 8)
|
||||
+ "-" + withoutDashes.substring(8, 12)
|
||||
+ "-" + withoutDashes.substring(12, 16)
|
||||
+ "-" + withoutDashes.substring(16, 20)
|
||||
+ "-" + withoutDashes.substring(20, 32));
|
||||
}
|
||||
|
||||
protected final Map<String, String> localeMessages = new ConcurrentHashMap<>();
|
||||
private MojangApiConnector mojangApiConnector;
|
||||
private Storage storage;
|
||||
|
||||
public void setMojangApiConnector(MojangApiConnector mojangApiConnector) {
|
||||
this.mojangApiConnector = mojangApiConnector;
|
||||
}
|
||||
|
||||
public MojangApiConnector getMojangApiConnector() {
|
||||
return mojangApiConnector;
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public abstract File getDataFolder();
|
||||
|
||||
public abstract Logger getLogger();
|
||||
|
||||
public abstract ThreadFactory getThreadFactory();
|
||||
|
||||
public String getMessage(String key) {
|
||||
return localeMessages.get(key);
|
||||
}
|
||||
|
||||
public abstract void loadMessages();
|
||||
|
||||
public abstract void loadConfig();
|
||||
|
||||
public boolean setupDatabase(String driver, String host, int port, String database, String user, String password) {
|
||||
storage = new Storage(this, driver, host, port, database, user, password);
|
||||
try {
|
||||
storage.createTables();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Failed to setup database. Disabling plugin...", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class MojangApiConnector {
|
||||
|
||||
//http connection, read timeout and user agent for a connection to mojang api servers
|
||||
private static final int TIMEOUT = 1 * 1_000;
|
||||
private static final String USER_AGENT = "Premium-Checker";
|
||||
|
||||
//only premium (paid account) users have a uuid from here
|
||||
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
||||
//this includes a-zA-Z1-9_
|
||||
private static final String VALID_PLAYERNAME = "^\\w{2,16}$";
|
||||
|
||||
//compile the pattern only on plugin enable -> and this have to be threadsafe
|
||||
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
|
||||
|
||||
protected final FastLoginCore plugin;
|
||||
|
||||
public MojangApiConnector(FastLoginCore plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param playerName
|
||||
* @return null on non-premium
|
||||
*/
|
||||
public UUID getPremiumUUID(String playerName) {
|
||||
//check if it's a valid playername
|
||||
if (playernameMatcher.matcher(playerName).matches()) {
|
||||
//only make a API call if the name is valid existing mojang account
|
||||
try {
|
||||
HttpURLConnection connection = getConnection(UUID_LINK + playerName);
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (line != null && !line.equals("null")) {
|
||||
return getUUIDFromJson(line);
|
||||
}
|
||||
}
|
||||
//204 - no content for not found
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to check if player has a paid account", ex);
|
||||
}
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract boolean hasJoinedServer(Object session, String serverId);
|
||||
|
||||
protected abstract UUID getUUIDFromJson(String json);
|
||||
|
||||
protected HttpURLConnection getConnection(String url) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(TIMEOUT);
|
||||
connection.setReadTimeout(2 * TIMEOUT);
|
||||
//the new Mojang API just uses json as response
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import java.util.UUID;
|
||||
|
||||
public class PlayerProfile {
|
||||
|
||||
private final String playerName;
|
||||
private String playerName;
|
||||
|
||||
private long userId;
|
||||
|
||||
@@ -13,8 +13,7 @@ public class PlayerProfile {
|
||||
private String lastIp;
|
||||
private long lastLogin;
|
||||
|
||||
public PlayerProfile(long userId, UUID uuid, String playerName, boolean premium
|
||||
, String lastIp, long lastLogin) {
|
||||
public PlayerProfile(long userId, UUID uuid, String playerName, boolean premium, String lastIp, long lastLogin) {
|
||||
this.userId = userId;
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
@@ -24,18 +23,17 @@ public class PlayerProfile {
|
||||
}
|
||||
|
||||
public PlayerProfile(UUID uuid, String playerName, boolean premium, String lastIp) {
|
||||
this.userId = -1;
|
||||
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.premium = premium;
|
||||
this.lastIp = lastIp;
|
||||
this(-1, uuid, playerName, premium, lastIp, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
public synchronized String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public synchronized void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
public synchronized long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SharedConfig {
|
||||
|
||||
private final Map<String, Object> configValues;
|
||||
|
||||
public SharedConfig(Map<String, Object> configValues) {
|
||||
this.configValues = configValues;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String path, T def) {
|
||||
Object val = configValues.get(path);
|
||||
return ( val != null ) ? (T) val : def;
|
||||
}
|
||||
|
||||
public <T> T get(String path) {
|
||||
return get(path, null);
|
||||
}
|
||||
|
||||
public Map<String, Object> getConfigValues() {
|
||||
return configValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
/**
|
||||
* Represents a supporting authentication plugin in BungeeCord and Bukkit/Spigot/... servers
|
||||
*
|
||||
* @param <P> either org.bukkit.entity.Player for Bukkit or net.md_5.bungee.api.connection.ProxiedPlayer for BungeeCord
|
||||
*/
|
||||
public interface AuthPlugin<P> {
|
||||
|
||||
/**
|
||||
* Login the premium (paid account) player after the player joined successfully the server.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* @param player the player that needs to be logged in
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
boolean forceLogin(P player);
|
||||
|
||||
/**
|
||||
* Forces a register in order to protect the paid account.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* After a successful registration the player should be logged
|
||||
* in too.
|
||||
*
|
||||
* The method will be called only for premium accounts.
|
||||
* So it's recommended to set additionally premium property
|
||||
* if possible.
|
||||
*
|
||||
* Background: If we don't register an account, cracked players
|
||||
* could steal the unregistered account from the paid
|
||||
* player account
|
||||
*
|
||||
* @param player the premium account
|
||||
* @param password a strong random generated password
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
boolean forceRegister(P player, String password);
|
||||
|
||||
/**
|
||||
* Checks whether an account exists for this player name.
|
||||
*
|
||||
* This check should check if a cracked player account exists
|
||||
* so we can be sure the premium player doesn't steal the account
|
||||
* of that player.
|
||||
*
|
||||
* This operation will be performed async while the player is
|
||||
* connecting.
|
||||
*
|
||||
* @param playerName player name
|
||||
* @return if the player has an account
|
||||
* @throws Exception if an error occurred
|
||||
*/
|
||||
boolean isRegistered(String playerName) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
|
||||
|
||||
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
.toCharArray();
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getRandomPassword(P player) {
|
||||
StringBuilder generatedPassword = new StringBuilder(8);
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
generatedPassword.append(PASSWORD_CHARACTERS[random.nextInt(PASSWORD_CHARACTERS.length - 1)]);
|
||||
}
|
||||
|
||||
return generatedPassword.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
public interface PasswordGenerator<P> {
|
||||
|
||||
String getRandomPassword(P player);
|
||||
}
|
||||
@@ -1,36 +1,73 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class AutoInImporter extends Importer {
|
||||
|
||||
private static final String PLUGIN_NAME = "AutoIn";
|
||||
|
||||
private static final String SQLITE_FILE = "plugins/" + PLUGIN_NAME + "/AutoIn_PlayerOptions.db";
|
||||
|
||||
private static final String USER_TABLE = "nicknames";
|
||||
private static final String UUID_TABLE = "uuids";
|
||||
private static final String SESSION_TABLE = "sessions";
|
||||
|
||||
public static String getSQLitePath() {
|
||||
return SQLITE_FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int importData(DataSource source, DataSource target, String targetTable) throws SQLException {
|
||||
Connection con = null;
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = source.getConnection();
|
||||
stmt = con.createStatement();
|
||||
int importedRows = stmt.executeUpdate("INSERT INTO " + targetTable + " SELECT"
|
||||
+ " name AS Name,"
|
||||
+ " enabledLogin AS Premium,"
|
||||
+ " '' AS LastIp,"
|
||||
+ " REPLACE(puuid, '-', '') AS UUID"
|
||||
+ " FROM " + USER_TABLE
|
||||
+ " JOIN " + UUID_TABLE
|
||||
+ " ON " + UUID_TABLE + ".id = " + UUID_TABLE + ".nickname_id");
|
||||
return importedRows;
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT name, protection, premium, puuid FROM " + USER_TABLE
|
||||
+ " LEFT JOIN " + " ("
|
||||
/* Prevent duplicates */
|
||||
+ "SELECT * FROM " + UUID_TABLE + " GROUP BY nickname_id"
|
||||
+ ") uuids"
|
||||
+ " ON " + USER_TABLE + ".id = uuids.nickname_id");
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean protection = resultSet.getBoolean(2);
|
||||
/* Enable premium authentication only for those who want to be auto logged in,
|
||||
so they have their cracked protection disabled */
|
||||
boolean premium = !protection && resultSet.getBoolean(3);
|
||||
String puuid = resultSet.getString(4);
|
||||
|
||||
/* FastLogin will also make lookups on the uuid column for name changes
|
||||
the old 1.6.2 version won't check if those user have premium enabled
|
||||
|
||||
so it could happen that a premium could steal the account if we don't do this
|
||||
|
||||
It seems the uuid is saved on autoin too if the player is cracked */
|
||||
PlayerProfile profile;
|
||||
if (premium) {
|
||||
profile = new PlayerProfile(UUID.fromString(puuid), name, premium, "");
|
||||
} else {
|
||||
profile = new PlayerProfile(null, name, premium, "");
|
||||
}
|
||||
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(con);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -11,23 +16,35 @@ public class BPAImporter extends Importer {
|
||||
private static final String DEFAULT_TABLE_NAME = "users";
|
||||
|
||||
@Override
|
||||
public int importData(DataSource source, DataSource target, String targetTable) throws SQLException {
|
||||
Connection con = null;
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = source.getConnection();
|
||||
stmt = con.createStatement();
|
||||
int importedRows = stmt.executeUpdate("INSERT INTO " + targetTable + " SELECT"
|
||||
+ " nick AS Name,"
|
||||
+ " NULL AS UUID,"
|
||||
+ " checked AS Premium,"
|
||||
+ " lastIP AS LastIp,"
|
||||
+ " FROM_UNIXTIME(lastJoined * 0.001) AS LastLogin"
|
||||
+ " FROM " + DEFAULT_TABLE_NAME);
|
||||
return importedRows;
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT "
|
||||
+ "nick, "
|
||||
+ "checked, "
|
||||
+ "lastIP, "
|
||||
+ "FROM_UNIXTIME(lastJoined * 0.001) AS LastLogin "
|
||||
+ "FROM " + DEFAULT_TABLE_NAME);
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean premium = resultSet.getBoolean(2);
|
||||
String lastIP = resultSet.getString(3);
|
||||
Timestamp lastLogin = resultSet.getTimestamp(4);
|
||||
|
||||
//uuid doesn't exist here
|
||||
PlayerProfile profile = new PlayerProfile(null, name, premium, lastIP);
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(con);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -11,23 +17,42 @@ public class ElDziAuthImporter extends Importer {
|
||||
private static final String TABLE_NAME = "accounts";
|
||||
|
||||
@Override
|
||||
public int importData(DataSource source, DataSource target, String targetTable) throws SQLException {
|
||||
Connection con = null;
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = source.getConnection();
|
||||
stmt = con.createStatement();
|
||||
int importedRows = stmt.executeUpdate("INSERT INTO " + targetTable + " SELECT"
|
||||
+ " nick AS Name,"
|
||||
+ " uuid AS UUID,"
|
||||
+ " premium AS Premium,"
|
||||
+ " lastIp AS LastIp,"
|
||||
+ " FROM_UNIXTIME(lastPlayed * 0.001) AS LastLogin"
|
||||
+ " FROM " + TABLE_NAME);
|
||||
return importedRows;
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT "
|
||||
+ "nick, "
|
||||
+ "premium, "
|
||||
+ "lastIP, "
|
||||
+ "FROM_UNIXTIME(lastPlayed * 0.001) AS LastLogin "
|
||||
+ "FROM " + TABLE_NAME);
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean premium = resultSet.getBoolean(2);
|
||||
String lastIP = resultSet.getString(3);
|
||||
Timestamp lastLogin = resultSet.getTimestamp(4);
|
||||
|
||||
String uuid = resultSet.getString(5);
|
||||
|
||||
PlayerProfile profile;
|
||||
if (premium) {
|
||||
profile = new PlayerProfile(UUID.fromString(uuid), name, premium, lastIP);
|
||||
} else {
|
||||
profile = new PlayerProfile(null, name, premium, "");
|
||||
}
|
||||
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(con);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
public enum ImportPlugin {
|
||||
|
||||
AUTO_IN(AutoInImporter.class),
|
||||
|
||||
BPA(BPAImporter.class),
|
||||
|
||||
ELDZI(ElDziAuthImporter.class);
|
||||
|
||||
private final Class<? extends Importer> importerClass;
|
||||
|
||||
ImportPlugin(Class<? extends Importer> importer) {
|
||||
this.importerClass = importer;
|
||||
}
|
||||
|
||||
public Class<? extends Importer> getImporter() {
|
||||
return importerClass;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public abstract class Importer {
|
||||
|
||||
public abstract int importData(DataSource source, DataSource target, String targetTable) throws SQLException;
|
||||
public abstract int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException;
|
||||
|
||||
protected void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.CompatibleCacheBuilder;
|
||||
import com.github.games647.fastlogin.core.SharedConfig;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.importer.AutoInImporter;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import com.github.games647.fastlogin.core.importer.Importer;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* @param <P> Player class
|
||||
*/
|
||||
public abstract class FastLoginCore<P> {
|
||||
|
||||
public static <K, V> ConcurrentMap<K, V> buildCache(int expireAfterWrite, int maxSize) {
|
||||
CompatibleCacheBuilder<Object, Object> builder = CompatibleCacheBuilder.newBuilder();
|
||||
|
||||
if (expireAfterWrite > 0) {
|
||||
builder.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (maxSize > 0) {
|
||||
builder.maximumSize(maxSize);
|
||||
}
|
||||
|
||||
return builder.build(CacheLoader.from(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
}));
|
||||
}
|
||||
|
||||
public static UUID parseId(String withoutDashes) {
|
||||
if (withoutDashes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return UUID.fromString(withoutDashes.substring(0, 8)
|
||||
+ "-" + withoutDashes.substring(8, 12)
|
||||
+ "-" + withoutDashes.substring(12, 16)
|
||||
+ "-" + withoutDashes.substring(16, 20)
|
||||
+ "-" + withoutDashes.substring(20, 32));
|
||||
}
|
||||
|
||||
protected final Map<String, String> localeMessages = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentMap<String, Object> pendingLogins = FastLoginCore.buildCache(5, 0);
|
||||
private final Set<UUID> pendingConfirms = Sets.newHashSet();
|
||||
private final SharedConfig sharedConfig;
|
||||
|
||||
private MojangApiConnector apiConnector;
|
||||
private AuthStorage storage;
|
||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
private AuthPlugin<P> authPlugin;
|
||||
|
||||
public FastLoginCore(Map<String, Object> config) {
|
||||
this.sharedConfig = new SharedConfig(config);
|
||||
}
|
||||
|
||||
public void setApiConnector() {
|
||||
List<String> ipAddresses = sharedConfig.get("ip-addresses");
|
||||
int requestLimit = sharedConfig.get("mojang-request-limit");
|
||||
this.apiConnector = makeApiConnector(getLogger(), ipAddresses, requestLimit);
|
||||
}
|
||||
|
||||
public MojangApiConnector getApiConnector() {
|
||||
return apiConnector;
|
||||
}
|
||||
|
||||
public AuthStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public abstract File getDataFolder();
|
||||
|
||||
public abstract Logger getLogger();
|
||||
|
||||
public abstract ThreadFactory getThreadFactory();
|
||||
|
||||
public String getMessage(String key) {
|
||||
return localeMessages.get(key);
|
||||
}
|
||||
|
||||
public abstract void loadMessages();
|
||||
|
||||
public abstract MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests);
|
||||
|
||||
public boolean setupDatabase() {
|
||||
String driver = sharedConfig.get("driver");
|
||||
String host = sharedConfig.get("host", "");
|
||||
int port = sharedConfig.get("port", 3306);
|
||||
String database = sharedConfig.get("database");
|
||||
|
||||
String user = sharedConfig.get("username", "");
|
||||
String password = sharedConfig.get("password", "");
|
||||
|
||||
storage = new AuthStorage(this, driver, host, port, database, user, password);
|
||||
try {
|
||||
storage.createTables();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Failed to setup database. Disabling plugin...", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean importDatabase(ImportPlugin plugin, boolean sqlite, AuthStorage storage, String host, String database
|
||||
, String username, String pass) {
|
||||
if (sqlite && (plugin == ImportPlugin.BPA || plugin == ImportPlugin.ELDZI)) {
|
||||
throw new IllegalArgumentException("These plugins doesn't support flat file databases");
|
||||
}
|
||||
|
||||
Importer importer;
|
||||
try {
|
||||
importer = plugin.getImporter().newInstance();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't not setup importer class", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (sqlite && plugin == ImportPlugin.AUTO_IN) {
|
||||
//load sqlite driver
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
|
||||
String jdbcUrl = "jdbc:sqlite:" + AutoInImporter.getSQLitePath();
|
||||
Connection con = DriverManager.getConnection(jdbcUrl);
|
||||
importer.importData(con, storage.getDataSource(), storage);
|
||||
return true;
|
||||
} else {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
|
||||
String jdbcUrl = "jdbc:mysql://" + host + "/" + database;
|
||||
Connection con = DriverManager.getConnection(jdbcUrl, username, pass);
|
||||
importer.importData(con, storage.getDataSource(), storage);
|
||||
return true;
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
getLogger().log(Level.SEVERE, "Cannot find SQL driver. Do you removed it?", ex);
|
||||
} catch (SQLException ex) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't import data. Aborting...", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public SharedConfig getConfig() {
|
||||
return sharedConfig;
|
||||
}
|
||||
|
||||
public PasswordGenerator<P> getPasswordGenerator() {
|
||||
return passwordGenerator;
|
||||
}
|
||||
|
||||
public void setPasswordGenerator(PasswordGenerator<P> passwordGenerator) {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
public ConcurrentMap<String, Object> getPendingLogins() {
|
||||
return pendingLogins;
|
||||
}
|
||||
|
||||
public Set<UUID> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
public AuthPlugin<P> getAuthPluginHook() {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.SharedConfig;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class JoinManagement<T, S extends LoginSource> {
|
||||
|
||||
protected final FastLoginCore<T> core;
|
||||
protected final AuthPlugin<T> authHook;
|
||||
|
||||
public JoinManagement(FastLoginCore<T> core, AuthPlugin<T> authHook) {
|
||||
this.core = core;
|
||||
this.authHook = authHook;
|
||||
}
|
||||
|
||||
public void onLogin(String username, S source) {
|
||||
PlayerProfile profile = core.getStorage().loadProfile(username);
|
||||
if (profile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SharedConfig config = core.getConfig();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
try {
|
||||
if (profile.getUserId() == -1) {
|
||||
if (core.getPendingLogins().containsKey(ip + username) && config.get("secondAttemptCracked", false)) {
|
||||
core.getLogger().log(Level.INFO, "Second attempt login -> cracked {0}", username);
|
||||
|
||||
//first login request failed so make a cracked session
|
||||
startCrackedSession(source, profile, username);
|
||||
return;
|
||||
}
|
||||
|
||||
UUID premiumUUID = null;
|
||||
if (config.get("nameChangeCheck", false) || config.get("autoRegister", false)) {
|
||||
core.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
premiumUUID = core.getApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
if (premiumUUID == null
|
||||
|| (!checkNameChange(source, username, premiumUUID)
|
||||
&& !checkPremiumName(source, username, profile))) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
if (core.getConfig().get("switchMode", false)) {
|
||||
source.kick(core.getMessage("switch-kick-message"));
|
||||
return;
|
||||
}
|
||||
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to check premium state", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPremiumName(S source, String username, PlayerProfile profile) throws Exception {
|
||||
if (core.getConfig().get("autoRegister", false) && (authHook == null || !authHook.isRegistered(username))) {
|
||||
requestPremiumLogin(source, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkNameChange(S source, String username, UUID premiumUUID) {
|
||||
//user not exists in the db
|
||||
if (core.getConfig().get("nameChangeCheck", false)) {
|
||||
PlayerProfile profile = core.getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
//uuid exists in the database
|
||||
core.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
|
||||
//update the username to the new one in the database
|
||||
profile.setPlayerName(username);
|
||||
|
||||
requestPremiumLogin(source, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void requestPremiumLogin(S source, PlayerProfile profile, String username, boolean registered);
|
||||
|
||||
public abstract void startCrackedSession(S source, PlayerProfile profile, String username);
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
public class LoginSession {
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class LoginSession {
|
||||
|
||||
private final String username;
|
||||
private final PlayerProfile profile;
|
||||
|
||||
private UUID uuid;
|
||||
|
||||
protected boolean registered;
|
||||
|
||||
public LoginSession(String username, boolean registered, PlayerProfile profile) {
|
||||
@@ -28,4 +35,22 @@ public class LoginSession {
|
||||
public PlayerProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the premium UUID of this player
|
||||
*
|
||||
* @return the premium UUID or null if not fetched
|
||||
*/
|
||||
public synchronized UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the online UUID if it's fetched
|
||||
*
|
||||
* @param uuid premium UUID
|
||||
*/
|
||||
public synchronized void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface LoginSource {
|
||||
|
||||
void setOnlineMode() throws Exception;
|
||||
|
||||
void kick(String message) throws Exception;
|
||||
|
||||
InetSocketAddress getAddress();
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.BalancedSSLFactory;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.CharStreams;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
public abstract class MojangApiConnector {
|
||||
|
||||
//http connection, read timeout and user agent for a connection to mojang api servers
|
||||
private static final int TIMEOUT = 3 * 1_000;
|
||||
private static final String USER_AGENT = "Premium-Checker";
|
||||
|
||||
private static final String MCAPI_UUID_URL = "https://mcapi.ca/uuid/player/";
|
||||
|
||||
//only premium (paid account) users have a uuid from here
|
||||
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
||||
//this includes a-zA-Z1-9_
|
||||
private static final String VALID_PLAYERNAME = "^\\w{2,16}$";
|
||||
|
||||
private static final int RATE_LIMIT_CODE = 429;
|
||||
|
||||
//compile the pattern only on plugin enable -> and this have to be threadsafe
|
||||
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
|
||||
|
||||
private final ConcurrentMap<Object, Object> requests = FastLoginCore.buildCache(10, -1);
|
||||
private final BalancedSSLFactory sslFactory;
|
||||
private final int rateLimit;
|
||||
private long lastRateLimit;
|
||||
|
||||
protected final Logger logger;
|
||||
|
||||
public MojangApiConnector(Logger logger, List<String> localAddresses, int rateLimit) {
|
||||
this.logger = logger;
|
||||
|
||||
if (rateLimit > 600) {
|
||||
this.rateLimit = 600;
|
||||
} else {
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
if (localAddresses.isEmpty()) {
|
||||
this.sslFactory = null;
|
||||
} else {
|
||||
Set<InetAddress> addresses = Sets.newHashSet();
|
||||
for (String localAddress : localAddresses) {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(localAddress);
|
||||
if (!address.isAnyLocalAddress()) {
|
||||
logger.log(Level.WARNING, "Submitted IP-Address is not local {0}", address);
|
||||
continue;
|
||||
}
|
||||
|
||||
addresses.add(address);
|
||||
} catch (UnknownHostException ex) {
|
||||
logger.log(Level.SEVERE, "IP-Address is unknown to us", ex);
|
||||
}
|
||||
}
|
||||
|
||||
this.sslFactory = new BalancedSSLFactory(HttpsURLConnection.getDefaultSSLSocketFactory(), addresses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param playerName
|
||||
* @return null on non-premium
|
||||
*/
|
||||
public UUID getPremiumUUID(String playerName) {
|
||||
//check if it's a valid playername
|
||||
if (playernameMatcher.matcher(playerName).matches()) {
|
||||
// only make a API call if the name is valid existing mojang account
|
||||
|
||||
if (requests.size() >= rateLimit || System.currentTimeMillis() - lastRateLimit < 1_000 * 60 * 10) {
|
||||
// plugin.getLogger().fine("STILL WAITING FOR RATE_LIMIT - TRYING Third-party API");
|
||||
return getUUIDFromAPI(playerName);
|
||||
}
|
||||
|
||||
requests.put(new Object(), new Object());
|
||||
|
||||
try {
|
||||
HttpsURLConnection connection = getConnection(UUID_LINK + playerName);
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (!line.equals("null")) {
|
||||
return FastLoginCore.parseId(getUUIDFromJson(line));
|
||||
}
|
||||
} else if (connection.getResponseCode() == RATE_LIMIT_CODE) {
|
||||
logger.info("RATE_LIMIT REACHED - TRYING THIRD-PARTY API");
|
||||
lastRateLimit = System.currentTimeMillis();
|
||||
return getUUIDFromAPI(playerName);
|
||||
}
|
||||
//204 - no content for not found
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Failed to check if player has a paid account", ex);
|
||||
}
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public UUID getUUIDFromAPI(String playerName) {
|
||||
try {
|
||||
HttpURLConnection httpConnection = getConnection(MCAPI_UUID_URL + playerName);
|
||||
|
||||
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
//cracked
|
||||
return null;
|
||||
}
|
||||
|
||||
Reader reader = new InputStreamReader(httpConnection.getInputStream());
|
||||
String json = CharStreams.toString(reader);
|
||||
return FastLoginCore.parseId(getUUIDFromJson(json));
|
||||
} catch (IOException iOException) {
|
||||
logger.log(Level.SEVERE, "Tried converting name->uuid from third-party api", iOException);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract boolean hasJoinedServer(LoginSession session, String serverId);
|
||||
|
||||
protected abstract String getUUIDFromJson(String json);
|
||||
|
||||
protected HttpsURLConnection getConnection(String url) throws IOException {
|
||||
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(TIMEOUT);
|
||||
connection.setReadTimeout(2 * TIMEOUT);
|
||||
//the new Mojang API just uses json as response
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
|
||||
if (sslFactory != null) {
|
||||
connection.setSSLSocketFactory(sslFactory);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,21 @@
|
||||
# For more information: https://github.com/games647/FastLogin#why-do-players-have-to-invoke-a-command
|
||||
autoRegister: false
|
||||
|
||||
# This is extra configuration option to the feature above. If we request a premium authentication from a player who
|
||||
# isn't actual premium but used a premium username, the player will disconnect with the reason "invalid session" or
|
||||
# "bad login".
|
||||
#
|
||||
# If you activate this, we are remembering this player and do not force another premium authentication if the player
|
||||
# tries to join again, so the player could join as cracked player.
|
||||
secondAttemptCracked: false
|
||||
|
||||
# New cracked players will be kicked from server. Good if you want switch from offline-mode to online-mode without
|
||||
# losing players!
|
||||
#
|
||||
# Existing cracked and premium players could still join your server. Moreover you could add playernames to a whitelist.
|
||||
# So that these cracked players could join too although they are new players.
|
||||
switchMode: false
|
||||
|
||||
# If this plugin detected that a player has a premium, it can also set the associated
|
||||
# uuid from that account. So if the players changes their usernames, they will still have
|
||||
# the same playerdata (inventory, permissions, ...)
|
||||
@@ -100,6 +115,42 @@ nameChangeCheck: false
|
||||
# ChangeSkin, SkinRestoer, ...
|
||||
forwardSkin: true
|
||||
|
||||
# Displays a warning message that this message SHOULD only be invoked by
|
||||
# users who actually are the owner of this account. So not by cracked players
|
||||
#
|
||||
# If they still want to invoke the command, they have to invoke /premium again
|
||||
premium-warning: true
|
||||
|
||||
# If you have autoRegister or nameChangeCheck enabled, you could be rate-limited by Mojang.
|
||||
# The requests of the both options will be only made by FastLogin if the username is unknown to the server
|
||||
# You are allowed to make 600 requests per 10-minutes (60 per minute)
|
||||
# If you own a big server this value could be too low
|
||||
# Once the limit is reached, new players are always logged in as cracked until the rate-limit is expired.
|
||||
# (to the next ten minutes)
|
||||
#
|
||||
# The limit is IP-wide. If you have multiple IPv4-Addreses you specify them here. FastLogin will then use it in rotating
|
||||
# order --> 5 different IP-addreses 5 * 600 per 10 minutes
|
||||
# If this list is empty only the default one will be used
|
||||
#
|
||||
# Lists are created like this:
|
||||
#ip-addresses:
|
||||
# - 192-168-0-2
|
||||
ip-addresses: []
|
||||
|
||||
# How many requests should be established until the plugin uses the third-party API https://mcapi.ca/
|
||||
# Once this number is reached in a range of ten minutes it will start connecting to https://mcapi.ca/ for the next ten minutes
|
||||
# This option exists in order to workaround the rate-limiting. Name -> UUID are fetched in the same way like heads
|
||||
#
|
||||
# If you want to join the discussion visit this: https://github.com/games647/FastLogin/issues/27#issuecomment-226954350
|
||||
mojang-request-limit: 600
|
||||
|
||||
# This disables the auto login from fastlogin. So a premium (like a paid account) authentication is requested, but
|
||||
# the player won't be auto logged into the account.
|
||||
#
|
||||
# This can be used as 2Factor authentication for better security of your accounts. A hacker then needs both passwords.
|
||||
# The password of your minecraft and the password to login in with your auth plugin
|
||||
autoLogin: true
|
||||
|
||||
# Database configuration
|
||||
# Recommened is the use of MariaDB (a better version of MySQL)
|
||||
|
||||
|
||||
@@ -21,18 +21,30 @@
|
||||
|
||||
# ========= Shared (BungeeCord and Bukkit) ============
|
||||
|
||||
# Switch mode is activated and a new (non-whitelist) cracked player tries to join
|
||||
switch-kick-message: '&4Only paid minecraft whitelisted accounts are allowed to join this server'
|
||||
|
||||
# Player activated premium logins in order to skip offline authentication
|
||||
add-premium: '&2Added to the list of premium players'
|
||||
|
||||
# Player activated premium logins in order to skip offline authentication
|
||||
add-premium-other: '&2Player has been added to the premium list'
|
||||
|
||||
# Player is already set be a paid account
|
||||
already-exists: '&4You are already on the premium list'
|
||||
|
||||
# Player is already set be a paid account
|
||||
already-exists-other: '&4Player is already on the premium list'
|
||||
|
||||
# Player was changed to be cracked
|
||||
remove-premium: '&2Removed from the list of premium players'
|
||||
|
||||
# Player is already set to be cracked
|
||||
not-premium: '&4You are not in the premium list'
|
||||
|
||||
# Player is already set to be cracked
|
||||
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'
|
||||
|
||||
@@ -74,6 +86,13 @@ invalid-session: '&4Invalid session'
|
||||
# The client sent a malicous packet without a login request packet
|
||||
invalid-requst: '&4Invalid request'
|
||||
|
||||
# Message if the bukkit isn't fully started to inject the packets
|
||||
not-started: '&cServer is not fully started yet. Please retry'
|
||||
|
||||
# Warning message if a user invoked /premium command
|
||||
premium-warning: '&c&lWARNING: &6This command should &lonly&6 be invoked if you are the owner of this paid minecraft account
|
||||
Type &a/premium&6 again to confirm'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
||||
|
||||
15
pom.xml
15
pom.xml
@@ -8,7 +8,7 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>1.5.2</version>
|
||||
<version>1.9</version>
|
||||
<inceptionYear>2015</inceptionYear>
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
@@ -50,21 +50,12 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<showWarnings>true</showWarnings>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<outputDirectory>${outputDir}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -43,6 +43,15 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<configuration>
|
||||
<outputDirectory>${outputDir}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user