Added /premium command

This commit is contained in:
games647
2015-09-06 20:31:44 +02:00
parent e51adf6528
commit 7d9ffa407a
8 changed files with 108 additions and 36 deletions

View File

@ -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/)

View File

@ -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();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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");
}
}
}

View File

@ -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()) {

View File

@ -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

View File

@ -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'