forked from TuxCoding/FastLogin
Added /premium command
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
# 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:
|
||||
* [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.github.games647.fastlogin;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
@ -23,11 +24,9 @@ import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/MinecraftEncryption.java
|
||||
* Source: https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/MinecraftEncryption.java
|
||||
*
|
||||
* Remapped by:
|
||||
* https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
||||
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
||||
*/
|
||||
public class Encryption {
|
||||
|
||||
@ -44,24 +43,15 @@ public class Encryption {
|
||||
}
|
||||
|
||||
public static byte[] getServerIdHash(String serverId, PublicKey publickey, SecretKey secretkey) {
|
||||
try {
|
||||
return digestOperation("SHA-1"
|
||||
, new byte[][]{serverId.getBytes("ISO_8859_1"), secretkey.getEncoded(), publickey.getEncoded()});
|
||||
} catch (UnsupportedEncodingException unsupportedencodingexception) {
|
||||
unsupportedencodingexception.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return digestOperation("SHA-1"
|
||||
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretkey.getEncoded(), publickey.getEncoded()});
|
||||
}
|
||||
|
||||
private static byte[] digestOperation(String algo, byte[]... content) {
|
||||
try {
|
||||
MessageDigest messagedigest = MessageDigest.getInstance(algo);
|
||||
int dataLength = content.length;
|
||||
|
||||
for (int i = 0; i < dataLength; ++i) {
|
||||
byte[] abyte1 = content[i];
|
||||
|
||||
messagedigest.update(abyte1);
|
||||
for (byte[] data : content) {
|
||||
messagedigest.update(data);
|
||||
}
|
||||
|
||||
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.StartPacketListener;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.reflect.ClassPath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
@ -27,6 +29,10 @@ public class FastLogin extends JavaPlugin {
|
||||
|
||||
//provide a immutable key pair to be thread safe
|
||||
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)
|
||||
//SafeCacheBuilder is used in order to be version independent
|
||||
private final ConcurrentMap<String, PlayerSession> session = SafeCacheBuilder.<String, PlayerSession>newBuilder()
|
||||
@ -62,17 +68,21 @@ public class FastLogin extends JavaPlugin {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
||||
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
||||
|
||||
//register commands
|
||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//clean up
|
||||
session.clear();
|
||||
enabledPremium.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a thread-safe map about players which are connecting
|
||||
* to the server are being checked to be premium (paid account)
|
||||
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
|
||||
* account)
|
||||
*
|
||||
* @return a thread-safe session map
|
||||
*/
|
||||
@ -90,8 +100,16 @@ public class FastLogin extends JavaPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a Mojang API connection. The connection is not
|
||||
* started in this method
|
||||
* Gets a set of user who activated premium logins
|
||||
*
|
||||
* @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
|
||||
* @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.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
@ -125,6 +124,8 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
if (hasJoinedServer(username, serverId)) {
|
||||
session.setVerified(true);
|
||||
|
||||
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||
|
||||
receiveFakeStartPacket(username, player);
|
||||
} else {
|
||||
//user tried to fake a authentification
|
||||
@ -146,15 +147,15 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
|
||||
private Object getNetworkManager(Player player)
|
||||
throws SecurityException, IllegalAccessException, NoSuchFieldException {
|
||||
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
Field declaredField = injector.getClass().getDeclaredField("injector");
|
||||
declaredField.setAccessible(true);
|
||||
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
Field injectorField = injector.getClass().getDeclaredField("injector");
|
||||
injectorField.setAccessible(true);
|
||||
|
||||
Object rawInjector = declaredField.get(injector);
|
||||
Object rawInjector = injectorField.get(injector);
|
||||
|
||||
declaredField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||
declaredField.setAccessible(true);
|
||||
return declaredField.get(rawInjector);
|
||||
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||
injectorField.setAccessible(true);
|
||||
return injectorField.get(rawInjector);
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
private void receiveFakeStartPacket(String username, Player player) {
|
||||
private void receiveFakeStartPacket(String username, Player from) {
|
||||
//fake a new login packet
|
||||
//see StartPacketListener for packet information
|
||||
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));
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
protocolManager.recieveClientPacket(player, startPacket, false);
|
||||
protocolManager.recieveClientPacket(from, startPacket, false);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", ex);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
player.kickPlayer("Error occurred");
|
||||
from.kickPlayer("Error occurred");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ public class PlayerListener implements Listener {
|
||||
final Player player = joinEvent.getPlayer();
|
||||
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
|
||||
if (session != null && session.getUsername().equals(player.getName())
|
||||
&& session.isVerified()) {
|
||||
|
@ -82,7 +82,8 @@ public class StartPacketListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
HttpURLConnection connection = plugin.getConnection(UUID_LINK + playerName);
|
||||
@ -93,12 +94,14 @@ public class StartPacketListener extends PacketAdapter {
|
||||
} catch (IOException 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;
|
||||
}
|
||||
|
||||
private void sentEncryptionRequest(String sessionKey, String username, Player player, PacketEvent packetEvent) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
try {
|
||||
/**
|
||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||
|
@ -17,3 +17,16 @@ softdepend:
|
||||
- AuthMe
|
||||
- CrazyLogin
|
||||
- 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