forked from TuxCoding/FastLogin
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
9fc7e0bf43 | |||
ac8bcb1758 | |||
bebcb3e9de | |||
0b899f61a8 | |||
7733135ce4 | |||
be89eec23b | |||
679060d4e9 | |||
f6aa064835 | |||
de4b73c3bd | |||
ac15829dcc | |||
0b709997a4 | |||
8809875ca4 | |||
aa30c070b9 | |||
51d0aefbf3 | |||
cb876a52bd | |||
3e844be65d | |||
dce95cf0d0 | |||
81eeaeae83 | |||
6b1542de88 | |||
99b7367366 | |||
961b144efb | |||
dcd06ad613 | |||
c4c043e1c5 | |||
87aa9dd668 | |||
2838c06ab3 | |||
ae58e0539a | |||
624745728f | |||
d0287ec2b4 | |||
e6a4af92cc | |||
8f3920fa99 | |||
a723b2ddd3 | |||
5cf67127c7 | |||
e5309b9fa1 | |||
e439126294 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -45,4 +45,5 @@ gradle-app.setting
|
||||
# Project module targets
|
||||
bukkit/target
|
||||
universal/target
|
||||
bungee/target
|
||||
bungee/target
|
||||
core/target
|
||||
|
44
CHANGELOG.md
44
CHANGELOG.md
@ -1,3 +1,45 @@
|
||||
######1.5.1
|
||||
|
||||
* Fixed BungeeCord support by correctly saving the proxy ids
|
||||
|
||||
######1.5
|
||||
|
||||
* Added localization
|
||||
* Fixed NPE on premium name check if it's pure cracked player
|
||||
* Fixed NPE in BungeeCord on cracked login for existing players
|
||||
* Fixed saving of existing cracked players
|
||||
|
||||
######1.4
|
||||
|
||||
* Added Bungee setAuthPlugin method
|
||||
* Added nameChangeCheck
|
||||
* Multiple BungeeCord support
|
||||
|
||||
######1.3.1
|
||||
|
||||
* Prevent thread create violation in BungeeCord
|
||||
|
||||
######1.3
|
||||
|
||||
* Added support for AuthMe 3.X
|
||||
* Fixed premium logins if the server is not fully started
|
||||
* Added other command argument to /premium and /cracked
|
||||
* Added support for LogIt
|
||||
* Fixed 1.7 Minecraft support by removing guava 11+ only features -> Cauldron support
|
||||
* Fixed BungeeCord support in Cauldron
|
||||
|
||||
######1.2.1
|
||||
|
||||
* Fix premium status change notification message on BungeeCord
|
||||
|
||||
######1.2
|
||||
|
||||
* Fix race condition in BungeeCord
|
||||
* Fix dead lock in xAuth
|
||||
* Added API methods for plugins to set their own password generator
|
||||
* Added API methods for plugins to set their own auth plugin hook
|
||||
=> Added support for AdvancedLogin
|
||||
|
||||
######1.1
|
||||
|
||||
* Make the configuration options also work under BungeeCord (premiumUUID, forwardSkin)
|
||||
@ -102,4 +144,4 @@
|
||||
* Added better error handling
|
||||
|
||||
#####0.1
|
||||
* First release
|
||||
* First release
|
||||
|
49
README.md
49
README.md
@ -3,7 +3,7 @@
|
||||
[](https://travis-ci.org/games647/FastLogin)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8ZBULMAPN7MZC)
|
||||
|
||||
Checks if a minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
|
||||
Checks if a Minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
|
||||
So they don't need to enter passwords. This is also called auto login (auto-login).
|
||||
|
||||
###Features:
|
||||
@ -15,34 +15,39 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
|
||||
* Forge/Sponge message support
|
||||
* Premium UUID support
|
||||
* Forwards Skins
|
||||
* Detect user name changed and will update the existing database record
|
||||
* BungeeCord support
|
||||
* Auto register new premium players
|
||||
* Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib
|
||||
* No client modifications needed
|
||||
* Good performance by using async non blocking operations
|
||||
* Locale messages
|
||||
* Free
|
||||
* Open source
|
||||
|
||||
***
|
||||
|
||||
###Commands:
|
||||
* /premium [player] Label the invoker as paid account
|
||||
* /cracked [player] Label the invoker as cracked account
|
||||
* /premium [player] Label the invoker or the argument as paid account
|
||||
* /cracked [player] Label the invoker or the argument as cracked account
|
||||
|
||||
###Permissions:
|
||||
* fastlogin.bukkit.command.premium
|
||||
* fastlogin.bukkit.command.cracked
|
||||
|
||||
###Requirements:
|
||||
* Plugin: [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/)
|
||||
* Plugin: [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/) or [ProtocolSupport](http://www.spigotmc.org/resources/protocolsupport.7201/)
|
||||
* Tested Bukkit/[Spigot](https://www.spigotmc.org) 1.9 (could also work with other versions)
|
||||
* Java 7+
|
||||
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
|
||||
* An auth plugin. Supported Plugins
|
||||
* An auth plugin. Supported plugins
|
||||
|
||||
####Bukkit/Spigot/PaperSPigot
|
||||
####Bukkit/Spigot/PaperSpigot
|
||||
|
||||
* [AuthMe](http://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||
* [AuthMe (both 5.X and 3.X)](http://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||
* [xAuth](http://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||
* [LogIt](https://github.com/XziomekX/LogIt)
|
||||
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
|
||||
* [CrazyLogin](http://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
||||
* [LoginSecurity](http://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
||||
* [RoyalAuth](http://dev.bukkit.org/bukkit-plugins/royalauth/)
|
||||
@ -58,10 +63,32 @@ https://www.spigotmc.org/resources/fastlogin.14153/history
|
||||
|
||||
***
|
||||
|
||||
###How to install
|
||||
|
||||
####Bukkit/Spigot/PaperSpigot
|
||||
|
||||
1. Download and install ProtocolLib
|
||||
2. Download and install FastLogin
|
||||
3. Set your server in offline mode by setting the value onlinemode in your server.properties to false
|
||||
|
||||
####BungeeCord/Waterfall
|
||||
|
||||
1. Activate BungeeCord in the Spigot configuration
|
||||
2. Restart your server
|
||||
3. Now there is proxy-whitelist file in the FastLogin folder
|
||||
Put your stats id from the BungeeCord config into this file
|
||||
4. Activate ipForward in your BungeeCord config
|
||||
5. Download and Install FastLogin on BungeeCord AND Spigot
|
||||
6. Check your database settings in the config of FastLogin on BungeeCord
|
||||
7. Set your proxy (BungeeCord) in offline mode by setting the value onlinemode in your config.yml to false
|
||||
8. (BungeeCord doesn't support SQLite per default, so you should change the configuration to MySQL or MariaDB)
|
||||
|
||||
***
|
||||
|
||||
###FAQ
|
||||
|
||||
####Index
|
||||
1. [How does minecraft logins work?](#how-does-minecraft-logins-work)
|
||||
1. [How does Minecraft logins work?](#how-does-minecraft-logins-work)
|
||||
2. [How does this plugin work?](#how-does-this-plugin-work)
|
||||
3. [Why does the plugin require offline mode?](#why-does-the-plugin-require-offline-mode)
|
||||
4. [Can cracked player join with premium usernames?](#can-cracked-player-join-with-premium-usernames)
|
||||
@ -137,11 +164,7 @@ and Mojang account. Then the command can be executed. So someone different canno
|
||||
by buying the username.
|
||||
|
||||
####Does the plugin have BungeeCord support?
|
||||
Yes it has. Just activate ipForward in your BungeeCord config and place the plugin in the plugins folder of
|
||||
Bukkit/Spigot and BungeeCord. Then you have fill your BungeeCord Id (from the Stats-Option in the BungeeCord config)
|
||||
into the whitelist file of your Bukkit/Spigot server. For security reasons, don't post this Id on Forums.
|
||||
|
||||
This plugin will automatically detect if BungeeCord is running and handle premium checks on BungeeCord.
|
||||
Yes it has. See the how to install above.
|
||||
|
||||
####Could premium players have a premium UUID and Skin?
|
||||
Since 0.7 both features are implemented. You can check the config.yml in order to activate it.
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.1</version>
|
||||
<version>1.5.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@ -40,6 +40,11 @@
|
||||
<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>
|
||||
@ -48,6 +53,13 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Server API-->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
@ -60,7 +72,7 @@
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
<version>4.0.1</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@ -89,6 +101,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.lenis0012</groupId>
|
||||
<artifactId>LoginSecurity-2</artifactId>
|
||||
<!--Old version 2.0-->
|
||||
<version>-9c09e73b7f-1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -98,6 +111,18 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.lucaseasedup.logit</groupId>
|
||||
<artifactId>LogIt</artifactId>
|
||||
<version>SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.RoyalDev</groupId>
|
||||
<artifactId>RoyalAuth</artifactId>
|
||||
|
@ -0,0 +1,59 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
public class BukkitCore extends FastLoginCore {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public BukkitCore(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return plugin.getLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadFactory getThreadFactory() {
|
||||
String pluginName = plugin.getName();
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(pluginName + " Database Pool Thread #%1$d")
|
||||
//Hikari create daemons by default
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessages() {
|
||||
plugin.saveResource("messages.yml", false);
|
||||
|
||||
File messageFile = new File(plugin.getDataFolder(), "messages.yml");
|
||||
YamlConfiguration messageConfig = YamlConfiguration.loadConfiguration(messageFile);
|
||||
for (String key : messageConfig.getKeys(false)) {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig() {
|
||||
plugin.saveDefaultConfig();
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
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;
|
||||
|
||||
@ -11,33 +13,37 @@ import org.apache.commons.lang.ArrayUtils;
|
||||
*
|
||||
* This session is invalid if the player disconnects or the login was successful
|
||||
*/
|
||||
public class PlayerSession {
|
||||
public class BukkitLoginSession extends LoginSession {
|
||||
|
||||
private final String username;
|
||||
private final String serverId;
|
||||
private final byte[] verifyToken;
|
||||
|
||||
private UUID uuid;
|
||||
private WrappedSignedProperty skinProperty;
|
||||
private boolean verified;
|
||||
private boolean registered;
|
||||
|
||||
public PlayerSession(String username, String serverId, byte[] verifyToken) {
|
||||
this.username = username;
|
||||
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
|
||||
, PlayerProfile profile) {
|
||||
super(username, registered, profile);
|
||||
|
||||
this.serverId = serverId;
|
||||
this.verifyToken = ArrayUtils.clone(verifyToken);
|
||||
}
|
||||
|
||||
public PlayerSession(String username) {
|
||||
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY);
|
||||
//available for bungeecord
|
||||
public BukkitLoginSession(String username, boolean registered) {
|
||||
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, registered, null);
|
||||
}
|
||||
|
||||
//cracked player
|
||||
public BukkitLoginSession(String username, PlayerProfile profile) {
|
||||
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, false, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the random generated server id. This makes sure the request
|
||||
* sent from the client is just for this server.
|
||||
* Gets the random generated server id. This makes sure the request sent from the client is just for this server.
|
||||
*
|
||||
* See this for details
|
||||
* http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
* See this for details http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
*
|
||||
* Empty if it's a BungeeCord connection
|
||||
*
|
||||
@ -58,15 +64,6 @@ public class PlayerSession {
|
||||
return ArrayUtils.clone(verifyToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the username the player sent to the server
|
||||
*
|
||||
* @return the client sent username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the premium skin of this player
|
||||
*
|
||||
@ -86,26 +83,7 @@ public class PlayerSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the account of this player already exists
|
||||
*
|
||||
* @param registered whether the account exists
|
||||
*/
|
||||
public synchronized void setRegistered(boolean registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the account of this player already exists.
|
||||
*
|
||||
* @return whether the account exists
|
||||
*/
|
||||
public synchronized boolean needsRegistration() {
|
||||
return !registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player has a premium (paid account) account
|
||||
* and valid session
|
||||
* Sets whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
* @param verified whether the player has valid session
|
||||
*/
|
||||
@ -122,7 +100,6 @@ public class PlayerSession {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the online UUID if it's fetched
|
||||
*
|
||||
@ -133,8 +110,7 @@ public class PlayerSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the player has a premium (paid account) account
|
||||
* and valid session
|
||||
* Get whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
* @return whether the player has a valid session
|
||||
*/
|
@ -0,0 +1,12 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,33 +1,27 @@
|
||||
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.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
||||
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.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.StartPacketListener;
|
||||
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.google.common.reflect.ClassPath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.KeyPair;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@ -38,41 +32,46 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
|
||||
private static final int WORKER_THREADS = 5;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
//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 Storage storage;
|
||||
private final FastLoginCore core = new BukkitCore(this);
|
||||
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, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
|
||||
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, PlayerSession>() {
|
||||
.build(new CacheLoader<String, BukkitLoginSession>() {
|
||||
|
||||
@Override
|
||||
public PlayerSession load(String key) throws Exception {
|
||||
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 final MojangApiConnector mojangApiConnector = new MojangApiConnector(this);
|
||||
|
||||
private PasswordGenerator passwordGenerator = new DefaultPasswordGenerator();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultConfig();
|
||||
core.setMojangApiConnector(new MojangApiBukkit(core));
|
||||
|
||||
core.loadConfig();
|
||||
core.loadMessages();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (getServer().getOnlineMode()) {
|
||||
//we need to require offline to prevent a session request for a offline player
|
||||
@ -81,31 +80,9 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Bukkit.spigot().getConfig().isBoolean("settings.bungeecord")) {
|
||||
bungeeCord = Bukkit.spigot().getConfig().getBoolean("settings.bungeecord");
|
||||
} else {
|
||||
Method getConfigMethod = FuzzyReflection.fromObject(getServer().spigot(), true)
|
||||
.getMethodByName("getSpigotConfig");
|
||||
getConfigMethod.setAccessible(true);
|
||||
YamlConfiguration spigotConfig = (YamlConfiguration) getConfigMethod.invoke(getServer().spigot());
|
||||
bungeeCord = spigotConfig.getBoolean("settings.bungeecord");
|
||||
}
|
||||
} catch (Exception | NoSuchMethodError ex) {
|
||||
getLogger().warning("Cannot check bungeecord support. You use a non-spigot build");
|
||||
}
|
||||
|
||||
boolean hookFound = registerHooks();
|
||||
if (bungeeCord) {
|
||||
getLogger().info("BungeeCord setting detected. No auth plugin is required");
|
||||
} else if (!hookFound) {
|
||||
getLogger().info("No auth plugin were found and bungeecord is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
@ -119,11 +96,7 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
String username = getConfig().getString("username", "");
|
||||
String password = getConfig().getString("password", "");
|
||||
|
||||
this.storage = new Storage(this, driver, host, port, database, username, password);
|
||||
try {
|
||||
storage.createTables();
|
||||
} catch (SQLException sqlEx) {
|
||||
getLogger().log(Level.SEVERE, "Failed to create database tables. Disabling plugin...", sqlEx);
|
||||
if (!core.setupDatabase(driver, host, port, database, username, password)) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
@ -144,6 +117,9 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
//delay dependency setup because we load the plugin very early where plugins are initialized yet
|
||||
getServer().getScheduler().runTask(this, new DelayedAuthHook(this));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new BukkitJoinListener(this), this);
|
||||
|
||||
//register commands using a unique name
|
||||
@ -161,13 +137,21 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
player.removeMetadata(getName(), this);
|
||||
}
|
||||
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
}
|
||||
|
||||
public String generateStringPassword() {
|
||||
return RandomStringUtils.random(8, true, true);
|
||||
public FastLoginCore getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
public String generateStringPassword(Player player) {
|
||||
return passwordGenerator.getRandomPassword(player);
|
||||
}
|
||||
|
||||
public void setPasswordGenerator(PasswordGenerator passwordGenerator) {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,7 +160,7 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
*
|
||||
* @return a thread-safe session map
|
||||
*/
|
||||
public ConcurrentMap<String, PlayerSession> getSessions() {
|
||||
public ConcurrentMap<String, BukkitLoginSession> getSessions() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@ -189,10 +173,6 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the auth plugin hook in order to interact with the plugins. This can be null if no supporting auth plugin
|
||||
* was found.
|
||||
@ -200,51 +180,38 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
* @return interface to any supported auth plugin
|
||||
*/
|
||||
public BukkitAuthPlugin getAuthPlugin() {
|
||||
if (authPlugin == null) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the a connection in order to access important features from the Mojang API.
|
||||
*
|
||||
* @return the connector instance
|
||||
*/
|
||||
public MojangApiConnector getApiConnector() {
|
||||
return mojangApiConnector;
|
||||
}
|
||||
|
||||
private boolean registerHooks() {
|
||||
BukkitAuthPlugin authPluginHook = null;
|
||||
try {
|
||||
String hooksPackage = this.getClass().getPackage().getName() + ".hooks";
|
||||
//Look through all classes in the hooks package and look for supporting plugins on the server
|
||||
for (ClassPath.ClassInfo clazzInfo : ClassPath.from(getClassLoader()).getTopLevelClasses(hooksPackage)) {
|
||||
//remove the hook suffix
|
||||
String pluginName = clazzInfo.getSimpleName().replace("Hook", "");
|
||||
Class<?> clazz = clazzInfo.load();
|
||||
//uses only member classes which uses AuthPlugin interface (skip interfaces)
|
||||
if (BukkitAuthPlugin.class.isAssignableFrom(clazz)
|
||||
//check only for enabled plugins. A single plugin could be disabled by plugin managers
|
||||
&& getServer().getPluginManager().isPluginEnabled(pluginName)) {
|
||||
authPluginHook = (BukkitAuthPlugin) clazz.newInstance();
|
||||
getLogger().log(Level.INFO, "Hooking into auth plugin: {0}", pluginName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException | IOException ex) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't load the integration class", ex);
|
||||
}
|
||||
|
||||
if (authPluginHook == null) {
|
||||
//run this check for exceptions (errors) and not found plugins
|
||||
getLogger().warning("No support offline Auth plugin found. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
authPlugin = authPluginHook;
|
||||
return true;
|
||||
public void setAuthPluginHook(BukkitAuthPlugin authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
}
|
||||
|
||||
public boolean isBungeeCord() {
|
||||
return bungeeCord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait before the server is fully started. This is workaround, because connections right on startup are not
|
||||
* injected by ProtocolLib
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isServerFullyStarted() {
|
||||
return serverStarted;
|
||||
}
|
||||
|
||||
public void setServerStarted() {
|
||||
if (!this.serverStarted) {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
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 java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinedServer(Object session, String serverId) {
|
||||
if (!(session instanceof BukkitLoginSession)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BukkitLoginSession playerSession = (BukkitLoginSession) session;
|
||||
try {
|
||||
String url = HAS_JOINED_URL + "username=" + playerSession.getUsername() + "&serverId=" + serverId;
|
||||
HttpURLConnection conn = getConnection(url);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (line != null && !line.equals("null")) {
|
||||
//validate parsing
|
||||
//http://wiki.vg/Protocol_Encryption#Server
|
||||
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
||||
String uuid = (String) userData.get("id");
|
||||
playerSession.setUuid(FastLoginCore.parseId(uuid));
|
||||
|
||||
JSONArray properties = (JSONArray) userData.get("properties");
|
||||
JSONObject skinProperty = (JSONObject) properties.get(0);
|
||||
|
||||
String propertyName = (String) skinProperty.get("name");
|
||||
if (propertyName.equals("textures")) {
|
||||
String skinValue = (String) skinProperty.get("value");
|
||||
String signature = (String) skinProperty.get("signature");
|
||||
playerSession.setSkin(WrappedSignedProperty.fromValues(propertyName, 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);
|
||||
}
|
||||
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getUUIDFromJson(String json) {
|
||||
JSONObject userData = (JSONObject) JSONValue.parse(json);
|
||||
String uuid = (String) userData.get("id");
|
||||
return FastLoginCore.parseId(uuid);
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
|
||||
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;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
public 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";
|
||||
|
||||
//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?";
|
||||
|
||||
//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);
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public MojangApiConnector(FastLoginBukkit 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")) {
|
||||
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
||||
String uuid = (String) userData.get("id");
|
||||
return parseId(uuid);
|
||||
}
|
||||
}
|
||||
//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 boolean hasJoinedServer(PlayerSession session, String serverId) {
|
||||
try {
|
||||
String url = HAS_JOINED_URL + "username=" + session.getUsername() + "&serverId=" + serverId;
|
||||
HttpURLConnection conn = getConnection(url);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (line != null && !line.equals("null")) {
|
||||
//validate parsing
|
||||
//http://wiki.vg/Protocol_Encryption#Server
|
||||
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
||||
String uuid = (String) userData.get("id");
|
||||
session.setUuid(parseId(uuid));
|
||||
|
||||
JSONArray properties = (JSONArray) userData.get("properties");
|
||||
JSONObject skinProperty = (JSONObject) properties.get(0);
|
||||
|
||||
String propertyName = (String) skinProperty.get("name");
|
||||
if (propertyName.equals("textures")) {
|
||||
String skinValue = (String) skinProperty.get("value");
|
||||
String signature = (String) skinProperty.get("signature");
|
||||
session.setSkin(WrappedSignedProperty.fromValues(propertyName, 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);
|
||||
}
|
||||
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
return false;
|
||||
}
|
||||
|
||||
private 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));
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(String url) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(TIMEOUT);
|
||||
connection.setReadTimeout(TIMEOUT);
|
||||
//the new Mojang API just uses json as response
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface PasswordGenerator {
|
||||
|
||||
String getRandomPassword(Player player);
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerProfile {
|
||||
|
||||
private final String playerName;
|
||||
|
||||
private long userId;
|
||||
|
||||
private UUID uuid;
|
||||
private boolean premium;
|
||||
private String lastIp;
|
||||
private 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;
|
||||
this.premium = premium;
|
||||
this.lastIp = lastIp;
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public synchronized long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
protected synchronized void setUserId(long generatedId) {
|
||||
this.userId = generatedId;
|
||||
}
|
||||
|
||||
public synchronized UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public synchronized void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public synchronized boolean isPremium() {
|
||||
return premium;
|
||||
}
|
||||
|
||||
public synchronized void setPremium(boolean premium) {
|
||||
this.premium = premium;
|
||||
}
|
||||
|
||||
public synchronized String getLastIp() {
|
||||
return lastIp;
|
||||
}
|
||||
|
||||
public synchronized void setLastIp(String lastIp) {
|
||||
this.lastIp = lastIp;
|
||||
}
|
||||
|
||||
public synchronized long getLastLogin() {
|
||||
return lastLogin;
|
||||
}
|
||||
|
||||
public synchronized void setLastLogin(long lastLogin) {
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Storage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private final ConcurrentMap<String, PlayerProfile> profileCache = SafeCacheBuilder
|
||||
.<String, PlayerProfile>newBuilder()
|
||||
.concurrencyLevel(20)
|
||||
.expireAfterAccess(30, TimeUnit.MINUTES)
|
||||
.build(new CacheLoader<String, PlayerProfile>() {
|
||||
@Override
|
||||
public PlayerProfile load(String key) throws Exception {
|
||||
//should be fetched manually
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
});
|
||||
|
||||
private final HikariDataSource dataSource;
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public Storage(FastLoginBukkit plugin, String driver, String host, int port, String databasePath
|
||||
, String user, String pass) {
|
||||
this.plugin = plugin;
|
||||
|
||||
HikariConfig databaseConfig = new HikariConfig();
|
||||
databaseConfig.setUsername(user);
|
||||
databaseConfig.setPassword(pass);
|
||||
databaseConfig.setDriverClassName(driver);
|
||||
|
||||
databasePath = databasePath.replace("{pluginDir}", plugin.getDataFolder().getAbsolutePath());
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (driver.contains("sqlite")) {
|
||||
jdbcUrl += "sqlite" + "://" + databasePath;
|
||||
databaseConfig.setConnectionTestQuery("SELECT 1");
|
||||
} else {
|
||||
jdbcUrl += "mysql" + "://" + host + ':' + port + '/' + databasePath;
|
||||
}
|
||||
|
||||
databaseConfig.setJdbcUrl(jdbcUrl);
|
||||
this.dataSource = new HikariDataSource(databaseConfig);
|
||||
}
|
||||
|
||||
public void createTables() throws SQLException {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
Statement statement = con.createStatement();
|
||||
String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " ("
|
||||
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
|
||||
+ "`UUID` CHAR(36), "
|
||||
+ "`Name` VARCHAR(16) NOT NULL, "
|
||||
+ "`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`) "
|
||||
+ ")";
|
||||
|
||||
if (dataSource.getJdbcUrl().contains("sqlite")) {
|
||||
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
|
||||
}
|
||||
|
||||
statement.executeUpdate(createDataStmt);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerProfile getProfile(String name, boolean fetch) {
|
||||
if (profileCache.containsKey(name)) {
|
||||
return profileCache.get(name);
|
||||
} else if (fetch) {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
PreparedStatement loadStatement = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE
|
||||
+ " WHERE `Name`=? LIMIT 1");
|
||||
loadStatement.setString(1, name);
|
||||
|
||||
ResultSet resultSet = loadStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
String unparsedUUID = resultSet.getString(2);
|
||||
UUID uuid;
|
||||
if (unparsedUUID == null) {
|
||||
uuid = null;
|
||||
} else {
|
||||
uuid = FastLoginBukkit.parseId(unparsedUUID);
|
||||
}
|
||||
|
||||
// String name = resultSet.getString(3);
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
profileCache.put(name, playerProfile);
|
||||
return playerProfile;
|
||||
} else {
|
||||
PlayerProfile crackedProfile = new PlayerProfile(null, name, false, "");
|
||||
profileCache.put(name, crackedProfile);
|
||||
return crackedProfile;
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean save(PlayerProfile playerProfile) {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
|
||||
UUID uuid = playerProfile.getUuid();
|
||||
|
||||
if (playerProfile.getUserId() == -1) {
|
||||
PreparedStatement saveStatement = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
if (uuid == null) {
|
||||
saveStatement.setString(1, null);
|
||||
} else {
|
||||
saveStatement.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStatement.setString(2, playerProfile.getPlayerName());
|
||||
saveStatement.setBoolean(3, playerProfile.isPremium());
|
||||
saveStatement.setString(4, playerProfile.getLastIp());
|
||||
saveStatement.execute();
|
||||
|
||||
ResultSet generatedKeys = saveStatement.getGeneratedKeys();
|
||||
if (generatedKeys != null && generatedKeys.next()) {
|
||||
playerProfile.setUserId(generatedKeys.getInt(1));
|
||||
}
|
||||
} else {
|
||||
PreparedStatement saveStatement = con.prepareStatement("UPDATE " + PREMIUM_TABLE
|
||||
+ " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?");
|
||||
|
||||
if (uuid == null) {
|
||||
saveStatement.setString(1, null);
|
||||
} else {
|
||||
saveStatement.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStatement.setString(2, playerProfile.getPlayerName());
|
||||
saveStatement.setBoolean(3, playerProfile.isPremium());
|
||||
saveStatement.setString(4, playerProfile.getLastIp());
|
||||
// saveStatement.setTimestamp(5, new Timestamp(playerProfile.getLastLogin()));
|
||||
|
||||
saveStatement.setLong(5, playerProfile.getUserId());
|
||||
saveStatement.execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
dataSource.close();
|
||||
profileCache.clear();
|
||||
}
|
||||
|
||||
private void closeQuietly(Connection con) {
|
||||
if (con != null) {
|
||||
try {
|
||||
con.close();
|
||||
} catch (SQLException sqlEx) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to close connection", sqlEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
@ -25,19 +25,19 @@ public class CrackedCommand implements CommandExecutor {
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player)) {
|
||||
//console or command block
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Only players can remove themselves from the premium list");
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-console"));
|
||||
return true;
|
||||
}
|
||||
|
||||
final Player player = (Player) sender;
|
||||
// UUID uuid = player.getUniqueId();
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord((Player) sender);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Sending request...");
|
||||
notifiyBungeeCord(sender, sender.getName());
|
||||
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.getStorage().getProfile(player.getName(), true);
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
|
||||
profile.setPremium(false);
|
||||
@ -45,7 +45,7 @@ public class CrackedCommand implements CommandExecutor {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getStorage().save(profile);
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -55,27 +55,61 @@ public class CrackedCommand implements CommandExecutor {
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "NOT IMPLEMENTED YET");
|
||||
//todo:
|
||||
// String playerName = args[0];
|
||||
// boolean existed = plugin.getEnabledPremium().remove(playerName);
|
||||
// if (existed) {
|
||||
// sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
|
||||
// notifiyBungeeCord((Player) sender);
|
||||
// } else {
|
||||
// sender.sendMessage(ChatColor.DARK_RED + "User is not in the premium list");
|
||||
// }
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(Player target) {
|
||||
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 notifiyBungeeCord(Player sender, String target) {
|
||||
if (plugin.isBungeeCord()) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
dataOutput.writeUTF("OFF");
|
||||
dataOutput.writeUTF(target);
|
||||
|
||||
target.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
|
||||
plugin.getLogger().info("No player online to send a plugin message to the proxy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
@ -30,19 +30,19 @@ public class PremiumCommand implements CommandExecutor {
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player)) {
|
||||
//console or command block
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Only players can add themselves as premium");
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-console"));
|
||||
return true;
|
||||
}
|
||||
|
||||
Player player = (Player) sender;
|
||||
// UUID uuid = player.getUniqueId();
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(player);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Sending request...");
|
||||
notifiyBungeeCord(sender, sender.getName());
|
||||
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.getStorage().getProfile(player.getName(), true);
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
|
||||
} else {
|
||||
@ -51,7 +51,7 @@ public class PremiumCommand implements CommandExecutor {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getStorage().save(profile);
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
|
||||
@ -61,27 +61,62 @@ public class PremiumCommand implements CommandExecutor {
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "NOT IMPLEMENTED YET");
|
||||
//todo: async load
|
||||
// String playerName = args[0];
|
||||
// boolean didntexist = plugin.getEnabledPremium().add(playerName);
|
||||
// if (!didntexist) {
|
||||
// sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
|
||||
// } else {
|
||||
// sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
|
||||
// }
|
||||
// notifiyBungeeCord();
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(Player target) {
|
||||
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 notifiyBungeeCord(Player sender, String target) {
|
||||
if (plugin.isBungeeCord()) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
dataOutput.writeUTF("ON");
|
||||
dataOutput.writeUTF(target);
|
||||
|
||||
target.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
|
||||
sender.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
|
||||
import fr.xephi.authme.api.API;
|
||||
import fr.xephi.authme.api.NewAPI;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
@ -13,23 +16,42 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public class AuthMeHook implements BukkitAuthPlugin {
|
||||
|
||||
private final boolean isNewAPIAvailable;
|
||||
|
||||
public AuthMeHook() {
|
||||
this.isNewAPIAvailable = ClassUtil.isPresent("fr.xephi.authme.api.NewAPI");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//skips registration and login
|
||||
NewAPI.getInstance().forceLogin(player);
|
||||
//commented because the operation above is performed async -> race conditions
|
||||
// return NewAPI.getInstance().isAuthenticated(player);
|
||||
if (isNewAPIAvailable) {
|
||||
NewAPI.getInstance().forceLogin(player);
|
||||
} else {
|
||||
API.forceLogin(player);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return NewAPI.getInstance().isRegistered(playerName);
|
||||
if (isNewAPIAvailable) {
|
||||
return NewAPI.getInstance().isRegistered(playerName);
|
||||
} else {
|
||||
return API.isRegistered(playerName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
NewAPI.getInstance().forceRegister(player, password);
|
||||
if (isNewAPIAvailable) {
|
||||
NewAPI.getInstance().forceRegister(player, password);
|
||||
} else {
|
||||
API.registerPlayer(player.getName(), password);
|
||||
forceLogin(player);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ import de.st_ddt.crazylogin.data.LoginPlayerData;
|
||||
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.bukkit.Bukkit;
|
||||
|
@ -7,6 +7,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Achievement;
|
||||
import org.bukkit.Effect;
|
||||
import org.bukkit.EntityEffect;
|
||||
|
@ -0,0 +1,36 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import io.github.lucaseasedup.logit.CancelledState;
|
||||
import io.github.lucaseasedup.logit.LogItCore;
|
||||
import io.github.lucaseasedup.logit.account.Account;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/XziomekX/LogIt
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: Unknown
|
||||
* Spigot: Unknown
|
||||
*/
|
||||
public class LogItHook implements BukkitAuthPlugin {
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
return LogItCore.getInstance().getSessionManager().startSession(player) == CancelledState.NOT_CANCELLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return LogItCore.getInstance().getAccountManager().isRegistered(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
Account account = new Account(player.getName());
|
||||
account.changePassword(password);
|
||||
account.setLastActiveDate(System.currentTimeMillis() / 1000);
|
||||
account.setRegistrationDate(System.currentTimeMillis() / 1000);
|
||||
return LogItCore.getInstance().getAccountManager().insertAccount(account) == CancelledState.NOT_CANCELLED;
|
||||
}
|
||||
}
|
@ -5,14 +5,13 @@ 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 org.bukkit.Bukkit;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
@ -27,6 +26,11 @@ import org.bukkit.entity.Player;
|
||||
public class LoginSecurityHook implements BukkitAuthPlugin {
|
||||
|
||||
protected final LoginSecurity securityPlugin = LoginSecurity.instance;
|
||||
// protected final boolean newVersion;
|
||||
|
||||
public LoginSecurityHook() {
|
||||
// this.newVersion = ClassUtil.isPresent("com.lenis0012.bukkit.loginsecurity.session.action.LoginAction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
@ -71,13 +75,13 @@ public class LoginSecurityHook implements BukkitAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(final Player player, final String password) {
|
||||
final DataManager dataManager = securityPlugin.data;
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
DataManager dataManager = securityPlugin.data;
|
||||
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
final String uuidString = playerUUID.toString().replace("-", "");
|
||||
final InetAddress ipAddress = player.getAddress().getAddress();
|
||||
final String passwordHash = securityPlugin.hasher.hash(password);
|
||||
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());
|
||||
|
@ -6,7 +6,6 @@ import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
|
@ -70,10 +70,7 @@ public class xAuthHook implements BukkitAuthPlugin {
|
||||
boolean registerSuccess = xAuthPlugin.getAuthClass(xAuthPlayer)
|
||||
.adminRegister(player.getName(), password, null);
|
||||
|
||||
if (registerSuccess) {
|
||||
//login in the player after registration
|
||||
return forceLogin(player);
|
||||
}
|
||||
return registerSuccess;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -81,7 +78,13 @@ public class xAuthHook implements BukkitAuthPlugin {
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
boolean success = future.get();
|
||||
if (success) {
|
||||
//login in the player after registration
|
||||
return forceLogin(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
xAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
|
@ -2,16 +2,18 @@ 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.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
|
||||
/**
|
||||
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
|
||||
@ -27,12 +29,18 @@ public class BukkitJoinListener implements Listener {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
final Player player = joinEvent.getPlayer();
|
||||
Player player = joinEvent.getPlayer();
|
||||
|
||||
//removing the session because we now use it
|
||||
final PlayerSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
WrappedSignedProperty skin = session.getSkin();
|
||||
@ -46,12 +54,4 @@ public class BukkitJoinListener implements Listener {
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin, player), DELAY_LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
final Player player = quitEvent.getPlayer();
|
||||
|
||||
//prevent memory leaks
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
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.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.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@ -31,11 +34,11 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
//null if whitelist is empty so bungeecord support is disabled
|
||||
private final UUID proxyId;
|
||||
private final Set<UUID> proxyIds;
|
||||
|
||||
public BungeeCordListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
this.proxyId = loadBungeeCordId();
|
||||
this.proxyIds = loadBungeeCordIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,17 +68,14 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
plugin.getLogger().log(Level.FINEST, "Received proxy id {0} from {1}", new Object[]{sourceId, player});
|
||||
|
||||
//fail if BungeeCord support is disabled (id = null)
|
||||
if (sourceId.equals(proxyId)) {
|
||||
final PlayerSession playerSession = new PlayerSession(playerName);
|
||||
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);
|
||||
playerSession.setRegistered(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
} else if ("AUTO_REGISTER".equalsIgnoreCase(subchannel)) {
|
||||
playerSession.setVerified(true);
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -83,6 +83,8 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
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) {
|
||||
@ -97,7 +99,7 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
public UUID loadBungeeCordId() {
|
||||
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();
|
||||
@ -106,10 +108,19 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
whitelistFile.createNewFile();
|
||||
}
|
||||
|
||||
String firstLine = Files.readFirstLine(whitelistFile, Charsets.UTF_8);
|
||||
if (firstLine != null && !firstLine.isEmpty()) {
|
||||
return UUID.fromString(firstLine.trim());
|
||||
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;
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to create file for Proxy whitelist", ex);
|
||||
} catch (Exception ex) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
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.PlayerProfile;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
@ -25,6 +25,7 @@ public class ProtocolSupportListener implements Listener {
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
plugin.setServerStarted();
|
||||
if (loginStartEvent.isLoginDenied()) {
|
||||
return;
|
||||
}
|
||||
@ -32,28 +33,50 @@ public class ProtocolSupportListener implements Listener {
|
||||
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(username);
|
||||
plugin.getSessions().remove(loginStartEvent.getAddress().toString());
|
||||
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true);
|
||||
if (playerProfile != null) {
|
||||
if (playerProfile.isPremium()) {
|
||||
if (playerProfile.getUserId() != -1) {
|
||||
startPremiumSession(username, loginStartEvent, true);
|
||||
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);
|
||||
}
|
||||
} else if (playerProfile.getUserId() == -1) {
|
||||
|
||||
//user not exists in the db
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
try {
|
||||
if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
||||
UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username);
|
||||
if (premiumUUID != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
startPremiumSession(username, loginStartEvent, false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,19 +86,19 @@ public class ProtocolSupportListener implements Listener {
|
||||
//skin was resolved -> premium player
|
||||
if (propertiesResolveEvent.hasProperty("textures")) {
|
||||
InetSocketAddress address = propertiesResolveEvent.getAddress();
|
||||
PlayerSession session = plugin.getSessions().get(address.toString());
|
||||
BukkitLoginSession session = plugin.getSessions().get(address.toString());
|
||||
if (session != null) {
|
||||
session.setVerified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered) {
|
||||
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered
|
||||
, PlayerProfile playerProfile) {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
|
||||
PlayerSession playerSession = new PlayerSession(playerName, null, null);
|
||||
playerSession.setRegistered(registered);
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, null, null, registered, playerProfile);
|
||||
plugin.getSessions().put(address.toString(), playerSession);
|
||||
if (plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
loginStartEvent.setUseOnlineModeUUID(true);
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
@ -9,9 +9,9 @@ import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@ -73,13 +73,10 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
//the player name is unknown to ProtocolLib (so getName() doesn't work) - now uses ip:port as key
|
||||
String uniqueSessionKey = player.getAddress().toString();
|
||||
PlayerSession session = plugin.getSessions().get(uniqueSessionKey);
|
||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
if (session == null) {
|
||||
disconnect(packetEvent, "Invalid request", Level.FINE
|
||||
, "Player {0} tried to send encryption response at invalid state"
|
||||
, player.getAddress());
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-requst"), true
|
||||
, "Player {0} tried to send encryption response at invalid state", player.getAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -101,7 +98,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
||||
|
||||
String username = session.getUsername();
|
||||
if (plugin.getApiConnector().hasJoinedServer(session, serverId)) {
|
||||
if (plugin.getCore().getMojangApiConnector().hasJoinedServer(session, serverId)) {
|
||||
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||
|
||||
session.setVerified(true);
|
||||
@ -109,7 +106,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
receiveFakeStartPacket(username, player);
|
||||
} else {
|
||||
//user tried to fake a authentication
|
||||
disconnect(packetEvent, "Invalid session", Level.FINE
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-session"), true
|
||||
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
||||
, session.getUsername(), player.getAddress(), serverId);
|
||||
}
|
||||
@ -118,7 +115,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
private void setPremiumUUID(PlayerSession session, Player player) {
|
||||
private void setPremiumUUID(BukkitLoginSession session, Player player) {
|
||||
UUID uuid = session.getUuid();
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && uuid != null) {
|
||||
try {
|
||||
@ -132,7 +129,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVerifyToken(PlayerSession session, PrivateKey privateKey, PacketEvent packetEvent) {
|
||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey, PacketEvent packetEvent) {
|
||||
byte[] requestVerify = session.getVerifyToken();
|
||||
//encrypted verify token
|
||||
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||
@ -140,9 +137,8 @@ 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, "Invalid token", Level.FINE
|
||||
, "Player {0} ({1}) tried to login with an invalid verify token. "
|
||||
+ "Server: {2} Client: {3}"
|
||||
disconnect(packetEvent, 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;
|
||||
}
|
||||
@ -171,23 +167,28 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
Object networkManager = getNetworkManager(player);
|
||||
|
||||
//try to detect the method by parameters
|
||||
Method encryptConnectionMethod = FuzzyReflection.fromObject(networkManager)
|
||||
.getMethodByParameters("a", SecretKey.class);
|
||||
Method encryptConnectionMethod = 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, "Error occurred", Level.SEVERE, "Couldn't enable encryption", ex);
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void disconnect(PacketEvent packetEvent, String kickReason, Level logLevel, String logMessage
|
||||
private void disconnect(PacketEvent packetEvent, String kickReason, boolean debugLevel, String logMessage
|
||||
, Object... arguments) {
|
||||
plugin.getLogger().log(logLevel, logMessage, arguments);
|
||||
if (debugLevel) {
|
||||
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);
|
||||
@ -222,7 +223,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
} 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, "Error occured");
|
||||
kickPlayer(from, plugin.getCore().getMessage("error-kick"));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
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.PlayerProfile;
|
||||
import com.github.games647.fastlogin.bukkit.PlayerSession;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.PublicKey;
|
||||
@ -61,7 +61,9 @@ public class StartPacketListener extends PacketAdapter {
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
final Player player = packetEvent.getPlayer();
|
||||
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();
|
||||
@ -71,38 +73,61 @@ public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
//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});
|
||||
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true);
|
||||
if (playerProfile != null) {
|
||||
if (playerProfile.isPremium()) {
|
||||
if (playerProfile.getUserId() != -1) {
|
||||
enablePremiumLogin(username, sessionKey, player, packetEvent, true);
|
||||
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);
|
||||
}
|
||||
} else if (playerProfile.getUserId() == -1) {
|
||||
|
||||
//user not exists in the db
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
try {
|
||||
if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
||||
UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username);
|
||||
if (premiumUUID != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
enablePremiumLogin(username, sessionKey, player, packetEvent, false);
|
||||
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, String sessionKey, Player player, PacketEvent packetEvent
|
||||
, boolean registered) {
|
||||
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);
|
||||
@ -113,8 +138,8 @@ public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
|
||||
if (success) {
|
||||
PlayerSession playerSession = new PlayerSession(username, serverId, verifyToken);
|
||||
playerSession.setRegistered(registered);
|
||||
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);
|
@ -0,0 +1,71 @@
|
||||
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.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class DelayedAuthHook implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public DelayedAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean hookFound = registerHooks();
|
||||
if (plugin.isBungeeCord()) {
|
||||
plugin.getLogger().info("BungeeCord setting detected. No auth plugin is required");
|
||||
} else if (!hookFound) {
|
||||
plugin.getLogger().warning("No auth plugin were found by this plugin "
|
||||
+ "(other plugins could hook into this after the intialization of this plugin)"
|
||||
+ "and bungeecord is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean registerHooks() {
|
||||
BukkitAuthPlugin authPluginHook = null;
|
||||
try {
|
||||
List<Class<? extends BukkitAuthPlugin>> supportedHooks = Lists.newArrayList(AuthMeHook.class
|
||||
, CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class
|
||||
, xAuthHook.class);
|
||||
for (Class<? extends BukkitAuthPlugin> 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) {
|
||||
//check only for enabled plugins. A single plugin could be disabled by plugin managers
|
||||
authPluginHook = clazz.newInstance();
|
||||
plugin.getLogger().log(Level.INFO, "Hooking into auth plugin: {0}", pluginName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Couldn't load the integration class", ex);
|
||||
}
|
||||
|
||||
if (authPluginHook == null) {
|
||||
//run this check for exceptions (errors) and not found plugins
|
||||
plugin.getLogger().warning("No support offline Auth plugin found. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.getAuthPlugin() == null) {
|
||||
plugin.setAuthPluginHook(authPluginHook);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
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.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.Storage;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
@ -10,12 +14,11 @@ import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ForceLoginTask implements Runnable {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
private final FastLoginBukkit plugin;
|
||||
protected final Player player;
|
||||
|
||||
public ForceLoginTask(FastLoginBukkit plugin, Player player) {
|
||||
@ -31,32 +34,24 @@ public class ForceLoginTask implements Runnable {
|
||||
|
||||
//remove the bungeecord identifier if there is ones
|
||||
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
|
||||
PlayerSession session = plugin.getSessions().get(id);
|
||||
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
|
||||
Storage storage = plugin.getStorage();
|
||||
PlayerProfile playerProfile = null;
|
||||
if (storage != null) {
|
||||
playerProfile = storage.getProfile(player.getName(), false);
|
||||
BukkitLoginSession session = plugin.getSessions().remove(id);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session == null) {
|
||||
//cracked player
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setUuid(null);
|
||||
playerProfile.setPremium(false);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
//check if it's the same player as we checked before
|
||||
} else if (player.getName().equals(session.getUsername())) {
|
||||
Storage 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();
|
||||
if (authPlugin == null) {
|
||||
//maybe only bungeecord plugin
|
||||
sendSuccessNotification();
|
||||
} else {
|
||||
boolean success = false;
|
||||
if (isOnlineThreadSafe() && session.isVerified()) {
|
||||
if (isOnlineThreadSafe()) {
|
||||
if (session.needsRegistration()) {
|
||||
success = forceRegister(authPlugin, player);
|
||||
} else {
|
||||
@ -76,23 +71,39 @@ public class ForceLoginTask implements Runnable {
|
||||
sendSuccessNotification();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//cracked player
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setUuid(null);
|
||||
playerProfile.setPremium(false);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forceRegister(BukkitAuthPlugin authPlugin, Player player) {
|
||||
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
|
||||
|
||||
String generatedPassword = plugin.generateStringPassword();
|
||||
String generatedPassword = plugin.generateStringPassword(player);
|
||||
boolean success = authPlugin.forceRegister(player, generatedPassword);
|
||||
player.sendMessage(ChatColor.DARK_GREEN + "Auto registered with password: " + generatedPassword);
|
||||
player.sendMessage(ChatColor.DARK_GREEN + "You may want change it?");
|
||||
String message = plugin.getCore().getMessage("auto-register");
|
||||
if (message != null) {
|
||||
message = message.replace("%password", generatedPassword);
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean forceLogin(BukkitAuthPlugin authPlugin, Player player) {
|
||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||
boolean success = authPlugin.forceLogin(player);
|
||||
player.sendMessage(ChatColor.DARK_GREEN + "Auto logged in");
|
||||
|
||||
String message = plugin.getCore().getMessage("auto-login");
|
||||
if (message != null) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
@ -11,18 +11,22 @@ description: |
|
||||
website: ${project.url}
|
||||
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:
|
||||
- ProtocolSupport
|
||||
# Auth plugins
|
||||
- xAuth
|
||||
- AuthMe
|
||||
- CrazyLogin
|
||||
- LoginSecurity
|
||||
- RoyalAuth
|
||||
- UltraAuth
|
||||
# - xAuth
|
||||
# - AuthMe
|
||||
# - LogIt
|
||||
# - CrazyLogin
|
||||
# - LoginSecurity
|
||||
# - RoyalAuth
|
||||
# - UltraAuth
|
||||
|
||||
commands:
|
||||
${project.parent.name}:
|
||||
@ -31,9 +35,9 @@ commands:
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.premium
|
||||
|
||||
unpremium:
|
||||
cracked:
|
||||
description: 'Label the invoker or the player specified as cracked if he was marked premium before'
|
||||
aliases: [cracked]
|
||||
aliases: [unpremium]
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.unpremium
|
||||
|
||||
@ -47,11 +51,11 @@ permissions:
|
||||
children:
|
||||
${project.artifactId}.command.premium: true
|
||||
|
||||
${project.artifactId}.command.unpremium:
|
||||
${project.artifactId}.command.cracked:
|
||||
description: 'Label themselves as cracked'
|
||||
default: true
|
||||
|
||||
${project.artifactId}.command..unpremium.other:
|
||||
${project.artifactId}.command..cracked.other:
|
||||
description: 'Label others as cracked'
|
||||
children:
|
||||
${project.artifactId}.command.unpremium: true
|
||||
${project.artifactId}.command.cracked: true
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.1</version>
|
||||
<version>1.5.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@ -37,6 +37,13 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
|
@ -0,0 +1,85 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
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.concurrent.ThreadFactory;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
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 {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public BungeeCore(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return plugin.getLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessages() {
|
||||
try {
|
||||
saveDefaultFile("messages.yml");
|
||||
|
||||
File messageFile = new File(getDataFolder(), "messages.yml");
|
||||
Configuration messageConfig = ConfigurationProvider.getProvider(YamlConfiguration.class).load(messageFile);
|
||||
for (String key : messageConfig.getKeys()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,19 +2,19 @@ package com.github.games647.fastlogin.bungee;
|
||||
|
||||
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.github.games647.fastlogin.core.LoginSession;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.md_5.bungee.Util;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
@ -26,41 +26,29 @@ import net.md_5.bungee.config.YamlConfiguration;
|
||||
*/
|
||||
public class FastLoginBungee extends Plugin {
|
||||
|
||||
private static final char[] CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
.toCharArray();
|
||||
|
||||
public static UUID parseId(String withoutDashes) {
|
||||
return Util.getUUID(withoutDashes);
|
||||
}
|
||||
|
||||
private final FastLoginCore loginCore = new BungeeCore(this);
|
||||
private BungeeAuthPlugin bungeeAuthPlugin;
|
||||
private final MojangApiConnector mojangApiConnector = new MojangApiConnector(this);
|
||||
private Storage storage;
|
||||
private Configuration configuration;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
private final ConcurrentMap<PendingConnection, Object> pendingAutoRegister = CacheBuilder
|
||||
private final ConcurrentMap<PendingConnection, LoginSession> session = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.<PendingConnection, Object>build().asMap();
|
||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||
.<PendingConnection, LoginSession>build().asMap();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
loginCore.setMojangApiConnector(new MojangApiBungee(loginCore));
|
||||
|
||||
File configFile = new File(getDataFolder(), "config.yml");
|
||||
if (!configFile.exists()) {
|
||||
try (InputStream in = getResourceAsStream("config.yml")) {
|
||||
Files.copy(in, configFile.toPath());
|
||||
} catch (IOException ioExc) {
|
||||
getLogger().log(Level.SEVERE, "Error saving default config", ioExc);
|
||||
}
|
||||
}
|
||||
loginCore.loadConfig();
|
||||
loginCore.loadMessages();
|
||||
|
||||
try {
|
||||
File configFile = new File(getDataFolder(), "config.yml");
|
||||
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
|
||||
|
||||
String driver = configuration.getString("driver");
|
||||
@ -70,14 +58,9 @@ public class FastLoginBungee extends Plugin {
|
||||
|
||||
String username = configuration.getString("username", "");
|
||||
String password = configuration.getString("password", "");
|
||||
storage = new Storage(this, driver, host, port, database, username, password);
|
||||
try {
|
||||
storage.createTables();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Failed to setup database. Disabling plugin...", ex);
|
||||
if (!loginCore.setupDatabase(driver, host, port, database, username, password)) {
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (IOException ioExc) {
|
||||
getLogger().log(Level.SEVERE, "Error loading config. Disabling plugin...", ioExc);
|
||||
return;
|
||||
@ -85,6 +68,7 @@ public class FastLoginBungee extends Plugin {
|
||||
|
||||
//events
|
||||
getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this));
|
||||
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
//this is required to listen to messages from the server
|
||||
getProxy().registerChannel(getDescription().getName());
|
||||
@ -95,7 +79,7 @@ public class FastLoginBungee extends Plugin {
|
||||
public String generateStringPassword() {
|
||||
StringBuilder generatedPassword = new StringBuilder(8);
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
generatedPassword.append(CHARACTERS[random.nextInt(CHARACTERS.length - 1)]);
|
||||
generatedPassword.append(PASSWORD_CHARACTERS[random.nextInt(PASSWORD_CHARACTERS.length - 1)]);
|
||||
}
|
||||
|
||||
return generatedPassword.toString();
|
||||
@ -103,25 +87,23 @@ public class FastLoginBungee extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
loginCore.close();
|
||||
}
|
||||
|
||||
public Configuration getConfiguration() {
|
||||
public FastLoginCore getCore() {
|
||||
return loginCore;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(BungeeAuthPlugin authPlugin) {
|
||||
this.bungeeAuthPlugin = authPlugin;
|
||||
}
|
||||
|
||||
public Configuration getConfig() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public MojangApiConnector getMojangApiConnector() {
|
||||
return mojangApiConnector;
|
||||
}
|
||||
|
||||
public ConcurrentMap<PendingConnection, Object> getPendingAutoRegister() {
|
||||
return pendingAutoRegister;
|
||||
public ConcurrentMap<PendingConnection, LoginSession> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.MojangApiConnector;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
|
||||
public class MojangApiBungee extends MojangApiConnector {
|
||||
|
||||
public MojangApiBungee(FastLoginCore plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getUUIDFromJson(String json) {
|
||||
MojangPlayer mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer.class);
|
||||
return FastLoginCore.parseId(mojangPlayer.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinedServer(Object session, String serverId) {
|
||||
//this is not needed in Bungee
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Enables online mode logins for specified users and sends
|
||||
* plugin message to the Bukkit version of this plugin in
|
||||
* order to clear that the connection is online mode.
|
||||
*/
|
||||
public class PlayerConnectionListener implements Listener {
|
||||
|
||||
protected final FastLoginBungee plugin;
|
||||
|
||||
public PlayerConnectionListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPreLogin(final PreLoginEvent preLoginEvent) {
|
||||
if (preLoginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
String username = connection.getName();
|
||||
try {
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true);
|
||||
if (playerProfile != null) {
|
||||
if (playerProfile.isPremium()) {
|
||||
if (playerProfile.getUserId() != -1) {
|
||||
connection.setOnlineMode(true);
|
||||
}
|
||||
} else if (playerProfile.getUserId() == -1) {
|
||||
//user not exists in the db
|
||||
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
|
||||
if (plugin.getConfiguration().getBoolean("autoRegister")
|
||||
&& (authPlugin == null || !authPlugin.isRegistered(username))) {
|
||||
UUID premiumUUID = plugin.getMojangApiConnector().getPremiumUUID(username);
|
||||
if (premiumUUID != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
connection.setOnlineMode(true);
|
||||
plugin.getPendingAutoRegister().put(connection, new Object());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to check premium state", ex);
|
||||
} finally {
|
||||
preLoginEvent.completeIntent(plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLogin(PostLoginEvent loginEvent) {
|
||||
ProxiedPlayer player = loginEvent.getPlayer();
|
||||
PendingConnection connection = player.getPendingConnection();
|
||||
String username = connection.getName();
|
||||
if (connection.isOnlineMode()) {
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(player.getName(), false);
|
||||
playerProfile.setUuid(player.getUniqueId());
|
||||
|
||||
//bungeecord will do this automatically so override it on disabled option
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
if (!plugin.getConfiguration().getBoolean("premiumUuid")) {
|
||||
try {
|
||||
UUID offlineUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(Charsets.UTF_8));
|
||||
|
||||
Field idField = initialHandler.getClass().getDeclaredField("uniqueId");
|
||||
idField.setAccessible(true);
|
||||
idField.set(connection, offlineUUID);
|
||||
|
||||
//bungeecord doesn't support overriding the premium uuid
|
||||
// connection.setUniqueId(offlineUUID);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to set offline uuid", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.getConfiguration().getBoolean("forwardSkin")) {
|
||||
//this is null on offline mode
|
||||
LoginResult loginProfile = initialHandler.getLoginProfile();
|
||||
if (loginProfile != null) {
|
||||
loginProfile.setProperties(new Property[]{});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
|
||||
ProxiedPlayer player = serverConnectedEvent.getPlayer();
|
||||
ForceLoginTask loginTask = new ForceLoginTask(plugin, player, serverConnectedEvent.getServer());
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
|
||||
String channel = pluginMessageEvent.getTag();
|
||||
if (pluginMessageEvent.isCancelled() || !plugin.getDescription().getName().equals(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//the client shouldn't be able to read the messages in order to know something about server internal states
|
||||
//moreover the client shouldn't be able fake a running premium check by sending the result message
|
||||
pluginMessageEvent.setCancelled(true);
|
||||
|
||||
//check if the message is sent from the server
|
||||
if (Server.class.isAssignableFrom(pluginMessageEvent.getSender().getClass())) {
|
||||
byte[] data = pluginMessageEvent.getData();
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
String subchannel = dataInput.readUTF();
|
||||
|
||||
final ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
|
||||
if ("ON".equals(subchannel)) {
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(forPlayer.getName(), true);
|
||||
if (playerProfile.isPremium()) {
|
||||
if (forPlayer.isConnected()) {
|
||||
TextComponent textComponent = new TextComponent("You are already on the premium list");
|
||||
textComponent.setColor(ChatColor.DARK_RED);
|
||||
forPlayer.sendMessage(textComponent);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(true);
|
||||
//todo: set uuid
|
||||
plugin.getStorage().save(playerProfile);
|
||||
}
|
||||
});
|
||||
} else if ("OFF".equals(subchannel)) {
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(forPlayer.getName(), true);
|
||||
if (!playerProfile.isPremium()) {
|
||||
if (forPlayer.isConnected()) {
|
||||
TextComponent textComponent = new TextComponent("You are not in the premium list");
|
||||
textComponent.setColor(ChatColor.DARK_RED);
|
||||
forPlayer.sendMessage(textComponent);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(false);
|
||||
playerProfile.setUuid(null);
|
||||
//todo: set uuid
|
||||
plugin.getStorage().save(playerProfile);
|
||||
TextComponent textComponent = new TextComponent("Added to the list of premium players");
|
||||
textComponent.setColor(ChatColor.DARK_GREEN);
|
||||
forPlayer.sendMessage(textComponent);
|
||||
}
|
||||
});
|
||||
} else if ("SUCCESS".equals(subchannel)) {
|
||||
if (forPlayer.getPendingConnection().isOnlineMode()) {
|
||||
//bukkit module successfully received and force logged in the user
|
||||
//update only on success to prevent corrupt data
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(forPlayer.getName(), false);
|
||||
playerProfile.setPremium(true);
|
||||
//we override this in the loginevent
|
||||
// playerProfile.setUuid(forPlayer.getUniqueId());
|
||||
plugin.getStorage().save(playerProfile);
|
||||
TextComponent textComponent = new TextComponent("Removed to the list of premium players");
|
||||
textComponent.setColor(ChatColor.DARK_GREEN);
|
||||
forPlayer.sendMessage(textComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Storage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private final ConcurrentMap<String, PlayerProfile> profileCache = CacheBuilder
|
||||
.<String, PlayerProfile>newBuilder()
|
||||
.concurrencyLevel(20)
|
||||
.expireAfterAccess(30, TimeUnit.MINUTES)
|
||||
.build(new CacheLoader<String, PlayerProfile>() {
|
||||
@Override
|
||||
public PlayerProfile load(String key) throws Exception {
|
||||
//should be fetched manually
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
}).asMap();
|
||||
|
||||
private final HikariDataSource dataSource;
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public Storage(FastLoginBungee plugin, String driver, String host, int port, String databasePath
|
||||
, String user, String pass) {
|
||||
this.plugin = plugin;
|
||||
|
||||
HikariConfig databaseConfig = new HikariConfig();
|
||||
databaseConfig.setUsername(user);
|
||||
databaseConfig.setPassword(pass);
|
||||
databaseConfig.setDriverClassName(driver);
|
||||
|
||||
databasePath = databasePath.replace("{pluginDir}", plugin.getDataFolder().getAbsolutePath());
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (driver.contains("sqlite")) {
|
||||
jdbcUrl += "sqlite" + "://" + databasePath;
|
||||
databaseConfig.setConnectionTestQuery("SELECT 1");
|
||||
} else {
|
||||
jdbcUrl += "mysql" + "://" + host + ':' + port + '/' + databasePath;
|
||||
}
|
||||
|
||||
databaseConfig.setJdbcUrl(jdbcUrl);
|
||||
this.dataSource = new HikariDataSource(databaseConfig);
|
||||
}
|
||||
|
||||
public void createTables() throws SQLException {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
Statement statement = con.createStatement();
|
||||
String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " ("
|
||||
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
|
||||
+ "`UUID` CHAR(36), "
|
||||
+ "`Name` VARCHAR(16) NOT NULL, "
|
||||
+ "`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`) "
|
||||
+ ")";
|
||||
|
||||
if (dataSource.getJdbcUrl().contains("sqlite")) {
|
||||
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
|
||||
}
|
||||
|
||||
statement.executeUpdate(createDataStmt);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerProfile getProfile(String name, boolean fetch) {
|
||||
if (profileCache.containsKey(name)) {
|
||||
return profileCache.get(name);
|
||||
} else if (fetch) {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
PreparedStatement loadStatement = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE
|
||||
+ " WHERE `Name`=? LIMIT 1");
|
||||
loadStatement.setString(1, name);
|
||||
|
||||
ResultSet resultSet = loadStatement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
String unparsedUUID = resultSet.getString(2);
|
||||
UUID uuid;
|
||||
if (unparsedUUID == null) {
|
||||
uuid = null;
|
||||
} else {
|
||||
uuid = FastLoginBungee.parseId(unparsedUUID);
|
||||
}
|
||||
|
||||
// String name = resultSet.getString(3);
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
profileCache.put(name, playerProfile);
|
||||
return playerProfile;
|
||||
} else {
|
||||
PlayerProfile crackedProfile = new PlayerProfile(null, name, false, "");
|
||||
profileCache.put(name, crackedProfile);
|
||||
return crackedProfile;
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean save(PlayerProfile playerProfile) {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
|
||||
UUID uuid = playerProfile.getUuid();
|
||||
|
||||
if (playerProfile.getUserId() == -1) {
|
||||
PreparedStatement saveStatement = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
if (uuid == null) {
|
||||
saveStatement.setString(1, null);
|
||||
} else {
|
||||
saveStatement.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStatement.setString(2, playerProfile.getPlayerName());
|
||||
saveStatement.setBoolean(3, playerProfile.isPremium());
|
||||
saveStatement.setString(4, playerProfile.getLastIp());
|
||||
saveStatement.execute();
|
||||
|
||||
ResultSet generatedKeys = saveStatement.getGeneratedKeys();
|
||||
if (generatedKeys != null && generatedKeys.next()) {
|
||||
playerProfile.setUserId(generatedKeys.getInt(1));
|
||||
}
|
||||
} else {
|
||||
PreparedStatement saveStatement = con.prepareStatement("UPDATE " + PREMIUM_TABLE
|
||||
+ " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?");
|
||||
|
||||
if (uuid == null) {
|
||||
saveStatement.setString(1, null);
|
||||
} else {
|
||||
saveStatement.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStatement.setString(2, playerProfile.getPlayerName());
|
||||
saveStatement.setBoolean(3, playerProfile.isPremium());
|
||||
saveStatement.setString(4, playerProfile.getLastIp());
|
||||
// saveStatement.setTimestamp(5, new Timestamp(playerProfile.getLastLogin()));
|
||||
|
||||
saveStatement.setLong(5, playerProfile.getUserId());
|
||||
saveStatement.execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
dataSource.close();
|
||||
profileCache.clear();
|
||||
}
|
||||
|
||||
private void closeQuietly(Connection con) {
|
||||
if (con != null) {
|
||||
try {
|
||||
con.close();
|
||||
} catch (SQLException sqlEx) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to close connection", sqlEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,14 +25,14 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
private final Tables databaseConnection = new Tables();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final ProxiedPlayer player) {
|
||||
public boolean forceLogin(ProxiedPlayer player) {
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L92-95
|
||||
Main.plonline.add(player.getName());
|
||||
|
||||
//renamed from ct to databaseConnection
|
||||
// databaseConnection.setStatus(player.getName(), "online");
|
||||
final Class<?>[] parameterTypes = new Class<?>[]{String.class, String.class};
|
||||
final Object[] arguments = new Object[]{player.getName(), "online"};
|
||||
Class<?>[] parameterTypes = new Class<?>[]{String.class, String.class};
|
||||
Object[] arguments = new Object[]{player.getName(), "online"};
|
||||
|
||||
try {
|
||||
callProtected("setStatus", parameterTypes, arguments);
|
||||
@ -56,7 +56,7 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(final ProxiedPlayer player, String password) {
|
||||
public boolean forceRegister(ProxiedPlayer player, String password) {
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L102
|
||||
PasswordHandler ph = new PasswordHandler();
|
||||
Random rand = new Random();
|
||||
@ -77,9 +77,9 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
//renamed t to databaseConnection
|
||||
// databaseConnection.newPlayerEntry(player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen);
|
||||
|
||||
final Class<?>[] parameterTypes = new Class<?>[] {String.class, String.class, String.class, String.class
|
||||
Class<?>[] parameterTypes = new Class<?>[] {String.class, String.class, String.class, String.class
|
||||
, String.class, String.class, String.class, String.class};
|
||||
final Object[] arguments = new Object[] {player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen};
|
||||
Object[] arguments = new Object[] {player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen};
|
||||
|
||||
try {
|
||||
callProtected("newPlayerEntry", parameterTypes, arguments);
|
||||
|
@ -0,0 +1,91 @@
|
||||
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.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.UUID;
|
||||
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.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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Enables online mode logins for specified users and sends
|
||||
* plugin message to the Bukkit version of this plugin in
|
||||
* order to clear that the connection is online mode.
|
||||
*/
|
||||
public class PlayerConnectionListener implements Listener {
|
||||
|
||||
protected final FastLoginBungee plugin;
|
||||
|
||||
public PlayerConnectionListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPreLogin(PreLoginEvent preLoginEvent) {
|
||||
if (preLoginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new AsyncPremiumCheck(plugin, preLoginEvent));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLogin(PostLoginEvent loginEvent) {
|
||||
ProxiedPlayer player = loginEvent.getPlayer();
|
||||
PendingConnection connection = player.getPendingConnection();
|
||||
String username = connection.getName();
|
||||
if (connection.isOnlineMode()) {
|
||||
LoginSession session = plugin.getSession().get(connection);
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
playerProfile.setUuid(player.getUniqueId());
|
||||
|
||||
//bungeecord will do this automatically so override it on disabled option
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
if (!plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
try {
|
||||
UUID offlineUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(Charsets.UTF_8));
|
||||
|
||||
//bungeecord doesn't support overriding the premium uuid
|
||||
//so we have to do it with reflection
|
||||
Field idField = initialHandler.getClass().getDeclaredField("uniqueId");
|
||||
idField.setAccessible(true);
|
||||
idField.set(connection, offlineUUID);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to set offline uuid", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
//this is null on offline mode
|
||||
LoginResult loginProfile = initialHandler.getLoginProfile();
|
||||
if (loginProfile != null) {
|
||||
loginProfile.setProperties(new Property[]{});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
|
||||
ProxiedPlayer player = serverConnectedEvent.getPlayer();
|
||||
ForceLoginTask loginTask = new ForceLoginTask(plugin, player, serverConnectedEvent.getServer());
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.tasks.AsyncToggleMessage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PluginMessageListener implements Listener {
|
||||
|
||||
protected final FastLoginBungee plugin;
|
||||
|
||||
public PluginMessageListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
|
||||
String channel = pluginMessageEvent.getTag();
|
||||
if (pluginMessageEvent.isCancelled() || !plugin.getDescription().getName().equals(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//the client shouldn't be able to read the messages in order to know something about server internal states
|
||||
//moreover the client shouldn't be able fake a running premium check by sending the result message
|
||||
pluginMessageEvent.setCancelled(true);
|
||||
|
||||
//check if the message is sent from the server
|
||||
if (Server.class.isAssignableFrom(pluginMessageEvent.getSender().getClass())) {
|
||||
readMessage(pluginMessageEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void readMessage(PluginMessageEvent pluginMessageEvent) {
|
||||
byte[] data = pluginMessageEvent.getData();
|
||||
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);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("OFF".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, 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());
|
||||
playerProfile.setPremium(true);
|
||||
//we override this in the loginevent
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
|
||||
public class AsyncPremiumCheck implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final PreLoginEvent preLoginEvent;
|
||||
|
||||
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent) {
|
||||
this.plugin = plugin;
|
||||
this.preLoginEvent = preLoginEvent;
|
||||
}
|
||||
|
||||
@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 LoginSession(username, false, profile));
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
requestPremiumLogin(connection, profile, username, true);
|
||||
} else {
|
||||
plugin.getSession().put(connection, new LoginSession(username, false, profile));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to check premium state", ex);
|
||||
} 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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 LoginSession(username, register, profile));
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public class AsyncToggleMessage implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
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;
|
||||
this.fromPlayer = fromPlayer;
|
||||
this.targetPlayer = targetPlayer;
|
||||
this.toPremium = toPremium;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (toPremium) {
|
||||
activatePremium();
|
||||
} else {
|
||||
turnOffPremium();
|
||||
}
|
||||
}
|
||||
|
||||
private void turnOffPremium() {
|
||||
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(targetPlayer);
|
||||
if (!playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("not-premium")));
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(false);
|
||||
playerProfile.setUuid(null);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("remove-premium")));
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(targetPlayer);
|
||||
if (playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("already-exists")));
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("add-premium")));
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
|
||||
@ -23,17 +27,19 @@ public class ForceLoginTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PlayerProfile playerProfile = plugin.getStorage().getProfile(player.getName(), false);
|
||||
PendingConnection pendingConnection = player.getPendingConnection();
|
||||
LoginSession session = plugin.getSession().remove(pendingConnection);
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
//force login only on success
|
||||
if (player.getPendingConnection().isOnlineMode()) {
|
||||
boolean autoRegister = plugin.getPendingAutoRegister().remove(player.getPendingConnection()) != null;
|
||||
if (pendingConnection.isOnlineMode()) {
|
||||
boolean autoRegister = session.needsRegistration();
|
||||
|
||||
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
} else if (player.isConnected()) {
|
||||
if (autoRegister) {
|
||||
if (session.needsRegistration()) {
|
||||
String password = plugin.generateStringPassword();
|
||||
if (authPlugin.forceRegister(player, password)) {
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
@ -44,9 +50,8 @@ public class ForceLoginTask implements Runnable {
|
||||
}
|
||||
} else {
|
||||
//cracked player
|
||||
//update only on success to prevent corrupt data
|
||||
playerProfile.setPremium(false);
|
||||
plugin.getStorage().save(playerProfile);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
}
|
||||
}
|
||||
|
32
core/pom.xml
Normal file
32
core/pom.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>FastLoginCore</name>
|
||||
|
||||
<dependencies>
|
||||
<!--Database pooling-->
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>2.4.6</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>1.7.21</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,67 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
public class LoginSession {
|
||||
|
||||
private final String username;
|
||||
private final boolean registered;
|
||||
private final PlayerProfile profile;
|
||||
|
||||
public LoginSession(String username, boolean registered, PlayerProfile profile) {
|
||||
this.username = username;
|
||||
this.registered = registered;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value is always false if we authenticate the player with a cracked authentication
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean needsRegistration() {
|
||||
return !registered;
|
||||
}
|
||||
|
||||
public PlayerProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@ -11,17 +9,12 @@ import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
|
||||
public class MojangApiConnector {
|
||||
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";
|
||||
|
||||
//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?";
|
||||
|
||||
//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_
|
||||
@ -30,11 +23,9 @@ public class MojangApiConnector {
|
||||
//compile the pattern only on plugin enable -> and this have to be threadsafe
|
||||
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
protected final FastLoginCore plugin;
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public MojangApiConnector(FastLoginBungee plugin) {
|
||||
public MojangApiConnector(FastLoginCore plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@ -53,8 +44,7 @@ public class MojangApiConnector {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (line != null && !line.equals("null")) {
|
||||
MojangPlayer mojangPlayer = BungeeCord.getInstance().gson.fromJson(line, MojangPlayer.class);
|
||||
return FastLoginBungee.parseId(mojangPlayer.getId());
|
||||
return getUUIDFromJson(line);
|
||||
}
|
||||
}
|
||||
//204 - no content for not found
|
||||
@ -67,10 +57,14 @@ public class MojangApiConnector {
|
||||
return null;
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(String url) throws IOException {
|
||||
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(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);
|
@ -1,4 +1,4 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -40,7 +40,7 @@ public class PlayerProfile {
|
||||
return userId;
|
||||
}
|
||||
|
||||
protected synchronized void setUserId(long generatedId) {
|
||||
public synchronized void setUserId(long generatedId) {
|
||||
this.userId = generatedId;
|
||||
}
|
||||
|
@ -0,0 +1,241 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Storage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private final FastLoginCore core;
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
public Storage(FastLoginCore core, String driver, String host, int port, String databasePath
|
||||
, String user, String pass) {
|
||||
this.core = core;
|
||||
|
||||
HikariConfig databaseConfig = new HikariConfig();
|
||||
databaseConfig.setUsername(user);
|
||||
databaseConfig.setPassword(pass);
|
||||
databaseConfig.setDriverClassName(driver);
|
||||
databaseConfig.setThreadFactory(core.getThreadFactory());
|
||||
|
||||
databasePath = databasePath.replace("{pluginDir}", core.getDataFolder().getAbsolutePath());
|
||||
|
||||
databaseConfig.setThreadFactory(core.getThreadFactory());
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (driver.contains("sqlite")) {
|
||||
jdbcUrl += "sqlite" + "://" + databasePath;
|
||||
databaseConfig.setConnectionTestQuery("SELECT 1");
|
||||
} else {
|
||||
jdbcUrl += "mysql" + "://" + host + ':' + port + '/' + databasePath;
|
||||
}
|
||||
|
||||
databaseConfig.setJdbcUrl(jdbcUrl);
|
||||
this.dataSource = new HikariDataSource(databaseConfig);
|
||||
}
|
||||
|
||||
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), "
|
||||
+ "Name VARCHAR(16) NOT NULL, "
|
||||
+ "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) "
|
||||
+ ")";
|
||||
|
||||
if (dataSource.getJdbcUrl().contains("sqlite")) {
|
||||
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
|
||||
}
|
||||
|
||||
createStmt.executeUpdate(createDataStmt);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(createStmt);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerProfile loadProfile(String name) {
|
||||
Connection con = null;
|
||||
PreparedStatement loadStmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=?");
|
||||
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);
|
||||
}
|
||||
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
return playerProfile;
|
||||
} else {
|
||||
PlayerProfile crackedProfile = new PlayerProfile(null, name, false, "");
|
||||
return crackedProfile;
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(loadStmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public PlayerProfile loadProfile(UUID uuid) {
|
||||
Connection con = null;
|
||||
PreparedStatement loadStmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=?");
|
||||
loadStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
String name = resultSet.getString(3);
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
return playerProfile;
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(loadStmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean save(PlayerProfile playerProfile) {
|
||||
Connection con = null;
|
||||
PreparedStatement updateStmt = null;
|
||||
PreparedStatement saveStmt = null;
|
||||
|
||||
ResultSet generatedKeys = null;
|
||||
try {
|
||||
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);
|
||||
|
||||
if (uuid == null) {
|
||||
saveStmt.setString(1, null);
|
||||
} else {
|
||||
saveStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStmt.setString(2, playerProfile.getPlayerName());
|
||||
saveStmt.setBoolean(3, playerProfile.isPremium());
|
||||
saveStmt.setString(4, playerProfile.getLastIp());
|
||||
|
||||
saveStmt.execute();
|
||||
|
||||
generatedKeys = saveStmt.getGeneratedKeys();
|
||||
if (generatedKeys != null && generatedKeys.next()) {
|
||||
playerProfile.setUserId(generatedKeys.getInt(1));
|
||||
}
|
||||
} else {
|
||||
saveStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE
|
||||
+ " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?");
|
||||
|
||||
if (uuid == null) {
|
||||
saveStmt.setString(1, null);
|
||||
} else {
|
||||
saveStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStmt.setString(2, playerProfile.getPlayerName());
|
||||
saveStmt.setBoolean(3, playerProfile.isPremium());
|
||||
saveStmt.setString(4, playerProfile.getLastIp());
|
||||
|
||||
saveStmt.setLong(5, playerProfile.getUserId());
|
||||
saveStmt.execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(updateStmt);
|
||||
closeQuietly(saveStmt);
|
||||
closeQuietly(generatedKeys);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
private void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception closeEx) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to close connection", closeEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class AutoInImporter extends Importer {
|
||||
|
||||
private static final String USER_TABLE = "nicknames";
|
||||
private static final String UUID_TABLE = "uuids";
|
||||
private static final String SESSION_TABLE = "sessions";
|
||||
|
||||
@Override
|
||||
public int importData(DataSource source, DataSource target, String targetTable) throws SQLException {
|
||||
Connection con = null;
|
||||
Statement stmt = 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;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
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;
|
||||
Statement stmt = 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;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
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;
|
||||
Statement stmt = 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;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(con);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public abstract class Importer {
|
||||
|
||||
public abstract int importData(DataSource source, DataSource target, String targetTable) throws SQLException;
|
||||
|
||||
protected void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception ignore) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
# Source code: https://github.com/games647/FastLogin
|
||||
#
|
||||
# You can access the newest config here:
|
||||
# https://github.com/games647/FastLogin/blob/master/bukkit/src/main/resources/config.yml
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/config.yml
|
||||
|
||||
# Request a premium login without forcing the player to type a command
|
||||
#
|
||||
@ -40,6 +40,50 @@ autoRegister: false
|
||||
# This feature requires Cauldron, Spigot or a fork of Spigot (PaperSpigot, TacoSpigot)
|
||||
premiumUuid: false
|
||||
|
||||
# This will make an additional check (only for player names which are not in the database) against the mojang servers
|
||||
# in order to get the premium UUID. If that premium UUID is in the database, we can assume on sucessful login that the
|
||||
# player changed it's username and we just update the name in the database.
|
||||
# Examples:
|
||||
# #### Case 1
|
||||
# nameChangeCheck = false ----- autoRegister = false
|
||||
#
|
||||
# Player logins as cracked until the player invoked the command /premium. Then we could override the existing database
|
||||
# record.
|
||||
#
|
||||
# #### Case 2
|
||||
#
|
||||
# nameChangeCheck = true ----- autoRegister = false
|
||||
#
|
||||
# Connect the Mojang API and check what UUID the player has (UUID exists => Paid Minecraft account). If that UUID is in
|
||||
# the database it's an **existing player** and FastLogin can **assume** the player is premium and changed the username.
|
||||
# If it's not in the database, it's a new player and **could be a cracked player**. So we just use a offline mode
|
||||
# authentication for this player.
|
||||
#
|
||||
# **Limitation**: Cracked players who uses the new username of a paid account cannot join the server if the database
|
||||
# contains the old name. (Example: The owner of the paid account no longer plays on the server, but changed the username
|
||||
# in the meanwhile).
|
||||
#
|
||||
# #### Case 3
|
||||
#
|
||||
# nameChangeCheck = false ----- autoRegister = true
|
||||
#
|
||||
# We will always request a premium authentication if the username is unknown to us, but is in use by a paid minecraft
|
||||
# account. This means it's kind of a more aggressive check like nameChangeCheck = true and autoRegister = false, because
|
||||
# it request a premium authentication which are completely new to us, that even the premium UUID is not in our database.
|
||||
#
|
||||
# **Limitation**: see below
|
||||
#
|
||||
# #### Case 4
|
||||
#
|
||||
# nameChangeCheck = true ----- autoRegister = true
|
||||
#
|
||||
# Based on autoRegister it checks if the player name is premium and login using a premium authentication. After that
|
||||
# fastlogin receives the premium UUID and can update the database record.
|
||||
#
|
||||
# **Limitation from autoRegister**: New offline players who uses the username of an existing minecraft cannot join the
|
||||
# server.
|
||||
nameChangeCheck: false
|
||||
|
||||
# If your players have a premium account and a skin associated to their account, this plugin
|
||||
# can download the data and set it to the online player.
|
||||
#
|
||||
@ -64,7 +108,7 @@ driver: org.sqlite.JDBC
|
||||
# File location
|
||||
database: '{pluginDir}/FastLogin.db'
|
||||
|
||||
# MySQL and SQLite
|
||||
# MySQL
|
||||
#driver: com.mysql.jdbc.Driver
|
||||
#host: localhost
|
||||
#port: 3306
|
79
core/src/main/resources/messages.yml
Normal file
79
core/src/main/resources/messages.yml
Normal file
@ -0,0 +1,79 @@
|
||||
# FastLogin localization
|
||||
# Project site: https://www.spigotmc.org/resources/fastlogin.14153
|
||||
# Source code: https://github.com/games647/FastLogin
|
||||
#
|
||||
# You can access the newest locale here:
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/messages.yml
|
||||
#
|
||||
# You want to have language template? Visit the Github Wiki here:
|
||||
# https://github.com/games647/FastLogin/wiki/English
|
||||
|
||||
# In order to split a message into seperate lines you could just make a new line, but keep the '
|
||||
# Example:
|
||||
# bla: '&aFirst line
|
||||
# Second line
|
||||
# Third line'
|
||||
|
||||
# If you want to disable a message, you can just set it to a empty value.
|
||||
# In this case no message will be sent
|
||||
# Example:
|
||||
# bla: ''
|
||||
|
||||
# ========= Shared (BungeeCord and Bukkit) ============
|
||||
|
||||
# Player activated premium logins in order to skip offline authentication
|
||||
add-premium: '&2Added to the list of premium players'
|
||||
|
||||
# Player is already set be a paid account
|
||||
already-exists: '&4You are 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'
|
||||
|
||||
# Admin wanted to change the premium of a user that isn't known to the plugin
|
||||
player-unknown: '&4Player not in the database'
|
||||
|
||||
# ========= Bukkit/Spigot/PaperSpigot/TacoSpigot only ================================
|
||||
|
||||
# The user skipped the authentication, because it was a premium player
|
||||
auto-login: '&2Auto logged in'
|
||||
|
||||
# The user was auto registered on the first join. The user account will be registered to protect it from cracked players
|
||||
# The password can be used if the mojang servers are down and you still want your premium users to login (PLANNED)
|
||||
auto-register: '&2Auto registered with password: %password
|
||||
You may want change it?'
|
||||
|
||||
# Player is not able to toggle the premium state of other players
|
||||
no-permission: '&4Not enough permissions'
|
||||
|
||||
# Although the console can toggle the premium state, it's not possible for the console itself.
|
||||
# Because the console is not a user. (obviously, isn't it?)
|
||||
no-console: '&4You are not a player. You cannot toggle the premium state for YOURSELF as a console'
|
||||
|
||||
# The user wants to toggle premium state, but BungeeCord support is enabled. This means the server have to communicate
|
||||
# with the BungeeCord first which will establish a connection with the database server.
|
||||
wait-on-proxy: '&6Sending request...'
|
||||
|
||||
# When ProtocolLib is enabled and the plugin is unable to continue handling a login request after a requested premium
|
||||
# authentication. In this state the client expects a success packet with a encrypted connection or disconnect packet.
|
||||
# So we kick the player, if we cannot encrypt the connection. In other situation (example: premium name check),
|
||||
# the player will be just authenticated as cracked
|
||||
error-kick: '&4Error occured'
|
||||
|
||||
# The server sents a verify token within the premium authentication reqest. If this doesn't match on response,
|
||||
# it could be another client sending malicious packets
|
||||
invalid-verify-token: '&4Invalid token'
|
||||
|
||||
# The client sent no request join server request to the mojang servers which would proof that it's owner of that
|
||||
# acciunt. Only modified clients would do this.
|
||||
invalid-session: '&4Invalid session'
|
||||
|
||||
# The client sent a malicous packet without a login request packet
|
||||
invalid-requst: '&4Invalid request'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
21
pom.xml
21
pom.xml
@ -8,7 +8,7 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>1.1</version>
|
||||
<version>1.5.1</version>
|
||||
<inceptionYear>2015</inceptionYear>
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
@ -22,6 +22,7 @@
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>bukkit</module>
|
||||
<module>bungee</module>
|
||||
<module>universal</module>
|
||||
@ -59,7 +60,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<outputDirectory>${outputDir}</outputDirectory>
|
||||
</configuration>
|
||||
@ -83,20 +84,4 @@
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!--Database pooling-->
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>2.4.6</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>1.7.20</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.1</version>
|
||||
<version>1.5.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@ -47,6 +47,12 @@
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.bukkit</artifactId>
|
||||
|
Reference in New Issue
Block a user