mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-30 02:37:34 +02:00
Added /premium command
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
# FastLogin
|
# FastLogin
|
||||||
|
|
||||||
Checks if a minecraft player has a valid paid account. If so, they can skip offline authentification.
|
Checks if a minecraft player has a valid premium (paid account). If so, they can skip offline authentification.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
* [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/)
|
* [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.github.games647.fastlogin;
|
package com.github.games647.fastlogin;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import com.google.common.base.Charsets;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
@ -23,11 +24,9 @@ import javax.crypto.spec.IvParameterSpec;
|
|||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source:
|
* Source: https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/MinecraftEncryption.java
|
||||||
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/MinecraftEncryption.java
|
|
||||||
*
|
*
|
||||||
* Remapped by:
|
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
||||||
* https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
|
||||||
*/
|
*/
|
||||||
public class Encryption {
|
public class Encryption {
|
||||||
|
|
||||||
@ -44,24 +43,15 @@ public class Encryption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] getServerIdHash(String serverId, PublicKey publickey, SecretKey secretkey) {
|
public static byte[] getServerIdHash(String serverId, PublicKey publickey, SecretKey secretkey) {
|
||||||
try {
|
return digestOperation("SHA-1"
|
||||||
return digestOperation("SHA-1"
|
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretkey.getEncoded(), publickey.getEncoded()});
|
||||||
, new byte[][]{serverId.getBytes("ISO_8859_1"), secretkey.getEncoded(), publickey.getEncoded()});
|
|
||||||
} catch (UnsupportedEncodingException unsupportedencodingexception) {
|
|
||||||
unsupportedencodingexception.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] digestOperation(String algo, byte[]... content) {
|
private static byte[] digestOperation(String algo, byte[]... content) {
|
||||||
try {
|
try {
|
||||||
MessageDigest messagedigest = MessageDigest.getInstance(algo);
|
MessageDigest messagedigest = MessageDigest.getInstance(algo);
|
||||||
int dataLength = content.length;
|
for (byte[] data : content) {
|
||||||
|
messagedigest.update(data);
|
||||||
for (int i = 0; i < dataLength; ++i) {
|
|
||||||
byte[] abyte1 = content[i];
|
|
||||||
|
|
||||||
messagedigest.update(abyte1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return messagedigest.digest();
|
return messagedigest.digest();
|
||||||
|
@ -8,12 +8,14 @@ import com.github.games647.fastlogin.hooks.AuthPlugin;
|
|||||||
import com.github.games647.fastlogin.listener.EncryptionPacketListener;
|
import com.github.games647.fastlogin.listener.EncryptionPacketListener;
|
||||||
import com.github.games647.fastlogin.listener.StartPacketListener;
|
import com.github.games647.fastlogin.listener.StartPacketListener;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.reflect.ClassPath;
|
import com.google.common.reflect.ClassPath;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@ -27,6 +29,10 @@ public class FastLogin extends JavaPlugin {
|
|||||||
|
|
||||||
//provide a immutable key pair to be thread safe
|
//provide a immutable key pair to be thread safe
|
||||||
private final KeyPair keyPair = Encryption.generateKeyPair();
|
private final KeyPair keyPair = Encryption.generateKeyPair();
|
||||||
|
|
||||||
|
//we need a thread-safe set because we access it async in the packet listener
|
||||||
|
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
|
||||||
|
|
||||||
//this map is thread-safe for async access (Packet Listener)
|
//this map is thread-safe for async access (Packet Listener)
|
||||||
//SafeCacheBuilder is used in order to be version independent
|
//SafeCacheBuilder is used in order to be version independent
|
||||||
private final ConcurrentMap<String, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
|
private final ConcurrentMap<String, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
|
||||||
@ -62,17 +68,21 @@ public class FastLogin extends JavaPlugin {
|
|||||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
||||||
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
||||||
|
|
||||||
|
//register commands
|
||||||
|
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
//clean up
|
//clean up
|
||||||
session.clear();
|
session.clear();
|
||||||
|
enabledPremium.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a thread-safe map about players which are connecting
|
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
|
||||||
* to the server are being checked to be premium (paid account)
|
* account)
|
||||||
*
|
*
|
||||||
* @return a thread-safe session map
|
* @return a thread-safe session map
|
||||||
*/
|
*/
|
||||||
@ -90,8 +100,16 @@ public class FastLogin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a Mojang API connection. The connection is not
|
* Gets a set of user who activated premium logins
|
||||||
* started in this method
|
*
|
||||||
|
* @return user who activated premium logins
|
||||||
|
*/
|
||||||
|
public Set<String> getEnabledPremium() {
|
||||||
|
return enabledPremium;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a Mojang API connection. The connection is not started in this method
|
||||||
*
|
*
|
||||||
* @param url the url connecting to
|
* @param url the url connecting to
|
||||||
* @return the prepared connection
|
* @return the prepared connection
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.github.games647.fastlogin;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class PremiumCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final FastLogin plugin;
|
||||||
|
|
||||||
|
public PremiumCommand(FastLogin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
if (!(sender instanceof Player)) {
|
||||||
|
//console or command block
|
||||||
|
sender.sendMessage(ChatColor.DARK_RED + "Only players can add themselves as premium");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String playerName = sender.getName();
|
||||||
|
plugin.getEnabledPremium().add(playerName);
|
||||||
|
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender.hasPermission(plugin.getName() + ".command." + command.getName() + ".others")) {
|
||||||
|
String playerName = args[0];
|
||||||
|
//todo check if valid username
|
||||||
|
plugin.getEnabledPremium().add(playerName);
|
||||||
|
sender.sendMessage(ChatColor.DARK_GREEN + "Added "
|
||||||
|
+ ChatColor.DARK_BLUE + ChatColor.BOLD + playerName
|
||||||
|
+ ChatColor.RESET + ChatColor.DARK_GREEN + " to the list of premium players");
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.DARK_RED + "Not enough permissions");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ import com.comphenix.protocol.ProtocolManager;
|
|||||||
import com.comphenix.protocol.events.PacketAdapter;
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
|
||||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||||
@ -125,6 +124,8 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
if (hasJoinedServer(username, serverId)) {
|
if (hasJoinedServer(username, serverId)) {
|
||||||
session.setVerified(true);
|
session.setVerified(true);
|
||||||
|
|
||||||
|
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||||
|
|
||||||
receiveFakeStartPacket(username, player);
|
receiveFakeStartPacket(username, player);
|
||||||
} else {
|
} else {
|
||||||
//user tried to fake a authentification
|
//user tried to fake a authentification
|
||||||
@ -146,15 +147,15 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
|
|
||||||
private Object getNetworkManager(Player player)
|
private Object getNetworkManager(Player player)
|
||||||
throws SecurityException, IllegalAccessException, NoSuchFieldException {
|
throws SecurityException, IllegalAccessException, NoSuchFieldException {
|
||||||
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||||
Field declaredField = injector.getClass().getDeclaredField("injector");
|
Field injectorField = injector.getClass().getDeclaredField("injector");
|
||||||
declaredField.setAccessible(true);
|
injectorField.setAccessible(true);
|
||||||
|
|
||||||
Object rawInjector = declaredField.get(injector);
|
Object rawInjector = injectorField.get(injector);
|
||||||
|
|
||||||
declaredField = rawInjector.getClass().getDeclaredField("networkManager");
|
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||||
declaredField.setAccessible(true);
|
injectorField.setAccessible(true);
|
||||||
return declaredField.get(rawInjector);
|
return injectorField.get(rawInjector);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasJoinedServer(String username, String serverId) {
|
private boolean hasJoinedServer(String username, String serverId) {
|
||||||
@ -177,11 +178,12 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
//catch not only ioexceptions also parse and NPE on unexpected json format
|
//catch not only ioexceptions also parse and NPE on unexpected json format
|
||||||
plugin.getLogger().log(Level.WARNING, "Failed to verify if session is valid", ex);
|
plugin.getLogger().log(Level.WARNING, "Failed to verify if session is valid", ex);
|
||||||
}
|
}
|
||||||
|
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveFakeStartPacket(String username, Player player) {
|
private void receiveFakeStartPacket(String username, Player from) {
|
||||||
//fake a new login packet
|
//fake a new login packet
|
||||||
//see StartPacketListener for packet information
|
//see StartPacketListener for packet information
|
||||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
|
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
|
||||||
@ -189,11 +191,11 @@ public class EncryptionPacketListener extends PacketAdapter {
|
|||||||
WrappedGameProfile fakeProfile = WrappedGameProfile.fromOfflinePlayer(Bukkit.getOfflinePlayer(username));
|
WrappedGameProfile fakeProfile = WrappedGameProfile.fromOfflinePlayer(Bukkit.getOfflinePlayer(username));
|
||||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||||
try {
|
try {
|
||||||
protocolManager.recieveClientPacket(player, startPacket, false);
|
protocolManager.recieveClientPacket(from, startPacket, false);
|
||||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||||
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", 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
|
//cancel the event in order to prevent the server receiving an invalid packet
|
||||||
player.kickPlayer("Error occurred");
|
from.kickPlayer("Error occurred");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ public class PlayerListener implements Listener {
|
|||||||
final Player player = joinEvent.getPlayer();
|
final Player player = joinEvent.getPlayer();
|
||||||
String address = player.getAddress().toString();
|
String address = player.getAddress().toString();
|
||||||
|
|
||||||
PlayerSession session = plugin.getSessions().get(address);
|
//removing the session because we now use it
|
||||||
|
PlayerSession session = plugin.getSessions().remove(address);
|
||||||
//check if it's the same player as we checked before
|
//check if it's the same player as we checked before
|
||||||
if (session != null && session.getUsername().equals(player.getName())
|
if (session != null && session.getUsername().equals(player.getName())
|
||||||
&& session.isVerified()) {
|
&& session.isVerified()) {
|
||||||
|
@ -82,7 +82,8 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPremium(String playerName) {
|
private boolean isPremium(String playerName) {
|
||||||
if (playernameMatcher.matcher(playerName).matches()) {
|
//check if it's a valid playername and the user activated fast logins
|
||||||
|
if (playernameMatcher.matcher(playerName).matches() && plugin.getEnabledPremium().contains(playerName)) {
|
||||||
//only make a API call if the name is valid existing mojang account
|
//only make a API call if the name is valid existing mojang account
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = plugin.getConnection(UUID_LINK + playerName);
|
HttpURLConnection connection = plugin.getConnection(UUID_LINK + playerName);
|
||||||
@ -93,12 +94,14 @@ public class StartPacketListener extends PacketAdapter {
|
|||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Failed to check if player has a paid account", 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 false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sentEncryptionRequest(String sessionKey, String username, Player player, PacketEvent packetEvent) {
|
private void sentEncryptionRequest(String sessionKey, String username, Player player, PacketEvent packetEvent) {
|
||||||
|
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||||
|
@ -17,3 +17,16 @@ softdepend:
|
|||||||
- AuthMe
|
- AuthMe
|
||||||
- CrazyLogin
|
- CrazyLogin
|
||||||
- LoginSecurity
|
- LoginSecurity
|
||||||
|
|
||||||
|
commands:
|
||||||
|
premium:
|
||||||
|
description: 'Marks the invoker or the player specified as premium'
|
||||||
|
aliases: [prem, fastlogin, loginfast]
|
||||||
|
usage: /<command> [player]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
${project.artifactId}.command.premium:
|
||||||
|
description: 'Mark themselves as premium using a command'
|
||||||
|
default: true
|
||||||
|
${project.artifactId}.command.premium.others:
|
||||||
|
description: 'Mark other people as premium'
|
Reference in New Issue
Block a user