Flesh out allowlist handling

Addresses comments from https://github.com/LOOHP/Limbo/pull/57#issuecomment-1304543589

In particular:

* Functionality now matches other server types

* only enforce the allowlist if a new "enforce-allowlist"
  boolean in server.properties is set to true

* Loads and process the allowlist only once when the server starts (or
  the reload command is executed), instead of every time a user connects.

* Add a new command & associated permissions "allowlist reload"
  to reload the allowlist
This commit is contained in:
Tad Hunt 2022-11-05 14:32:25 -06:00
parent 898fe20b14
commit d3b0aba94f
4 changed files with 118 additions and 85 deletions

View File

@ -432,6 +432,31 @@ public class Limbo {
public ServerProperties getServerProperties() { public ServerProperties getServerProperties() {
return properties; return properties;
} }
public void reloadAllowlist() {
properties.reloadAllowlist();
}
public boolean uuidIsAllowed(UUID requestedUuid) {
if (!properties.isEnforceAllowlist()) {
return true;
}
for (UUID allowedUuid : properties.getAllowlist()) {
if (requestedUuid.equals(allowedUuid)) {
if(!properties.isReducedDebugInfo()) {
Limbo.getInstance().getConsole().sendMessage(String.format("allowlist: %s allowed", requestedUuid.toString()));
}
return true;
}
}
if(!properties.isReducedDebugInfo()) {
Limbo.getInstance().getConsole().sendMessage(String.format("allowlist: %s not allowed", requestedUuid.toString()));
}
return false;
}
public ServerConnection getServerConnection() { public ServerConnection getServerConnection() {
return server; return server;

View File

@ -165,6 +165,20 @@ public class DefaultCommands implements CommandExecutor, TabCompletor {
} }
return; return;
} }
if (args[0].equalsIgnoreCase("allowlist")) {
if (sender.hasPermission("limboserver.allowlist")) {
if (args.length != 2) {
sender.sendMessage(ChatColor.RED + "Invalid usage!");
} else if (!args[1].equalsIgnoreCase("reload")) {
sender.sendMessage(ChatColor.RED + "Invalid usage!");
} else {
Limbo.getInstance().reloadAllowlist();
}
} else {
sender.sendMessage(ChatColor.RED + "You do not have permission to use that command!");
}
return;
}
} }
@Override @Override

View File

@ -22,20 +22,31 @@ package com.loohp.limbo.file;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import java.util.UUID;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.loohp.limbo.Console;
import com.loohp.limbo.Limbo; import com.loohp.limbo.Limbo;
import com.loohp.limbo.location.Location; import com.loohp.limbo.location.Location;
import com.loohp.limbo.utils.GameMode; import com.loohp.limbo.utils.GameMode;
@ -72,6 +83,8 @@ public class ServerProperties {
private int viewDistance; private int viewDistance;
private double ticksPerSecond; private double ticksPerSecond;
private boolean handshakeVerbose; private boolean handshakeVerbose;
private boolean enforceAllowlist;
private ArrayList<UUID> allowlist;
private String resourcePackSHA1; private String resourcePackSHA1;
private String resourcePackLink; private String resourcePackLink;
@ -184,9 +197,65 @@ public class ServerProperties {
favicon = Optional.empty(); favicon = Optional.empty();
} }
enforceAllowlist = Boolean.parseBoolean(prop.getProperty("enforce-allowlist"));
if (enforceAllowlist) {
reloadAllowlist();
}
Limbo.getInstance().getConsole().sendMessage("Loaded server.properties"); Limbo.getInstance().getConsole().sendMessage("Loaded server.properties");
} }
public void reloadAllowlist() {
Console console = Limbo.getInstance().getConsole();
allowlist = new ArrayList<UUID>();
try {
JSONParser parser = new JSONParser();
Object obj = parser.parse(new FileReader("allowlist.json"));
if (!(obj instanceof JSONArray)) {
console.sendMessage("allowlist: expected [] got {}");
return;
}
JSONArray array = (JSONArray) obj;
Iterator<?> iter = array.iterator();
while (iter.hasNext()) {
Object o = iter.next();
if (!(o instanceof JSONObject)) {
console.sendMessage("allowlist: array element is not an object");
continue;
}
JSONObject element = (JSONObject) o;
o = element.get("uuid");
if (o == null) {
console.sendMessage("allowlist: missing uuid attribute");
continue;
}
if (!(o instanceof String)) {
console.sendMessage("allowlist: uuid is not a string");
continue;
}
String uuidStr = (String) o;
UUID allowedUuid = UUID.fromString(uuidStr);
allowlist.add(allowedUuid);
}
} catch (IllegalArgumentException e) {
console.sendMessage(e.toString());
} catch (FileNotFoundException e) {
console.sendMessage(String.format("allowlist: %s", e.toString()));
} catch (IOException e) {
console.sendMessage(String.format("allowlist: %s", e.toString()));
} catch (ParseException e) {
console.sendMessage(String.format(" allowlist: parse: %s", e.toString()));
}
console.sendMessage("allowlist: reloaded");
}
public String getServerImplementationVersion() { public String getServerImplementationVersion() {
return Limbo.getInstance().SERVER_IMPLEMENTATION_VERSION; return Limbo.getInstance().SERVER_IMPLEMENTATION_VERSION;
} }
@ -294,6 +363,14 @@ public class ServerProperties {
public boolean handshakeVerboseEnabled() { public boolean handshakeVerboseEnabled() {
return handshakeVerbose; return handshakeVerbose;
} }
public boolean isEnforceAllowlist() {
return enforceAllowlist;
}
public ArrayList<UUID> getAllowlist() {
return allowlist;
}
public String getResourcePackLink() { public String getResourcePackLink() {
return resourcePackLink; return resourcePackLink;

View File

@ -19,7 +19,6 @@
package com.loohp.limbo.network; package com.loohp.limbo.network;
import com.loohp.limbo.Console;
import com.loohp.limbo.Limbo; import com.loohp.limbo.Limbo;
import com.loohp.limbo.events.player.PlayerJoinEvent; import com.loohp.limbo.events.player.PlayerJoinEvent;
import com.loohp.limbo.events.player.PlayerSpawnEvent; import com.loohp.limbo.events.player.PlayerSpawnEvent;
@ -98,14 +97,11 @@ import net.md_5.bungee.api.chat.TranslatableComponent;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser; import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInput; import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.net.InetAddress; import java.net.InetAddress;
@ -113,7 +109,6 @@ import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
@ -300,84 +295,6 @@ public class ClientConnection extends Thread {
}); });
} }
public boolean uuidAllowed(UUID uuid, boolean verbose) {
Console console = Limbo.getInstance().getConsole();
try {
JSONParser parser = new JSONParser();
Object obj = parser.parse(new FileReader("allowlist.json"));
if (!(obj instanceof JSONArray)) {
if (verbose) {
console.sendMessage("allowlist: expected [] got {}");
}
return false;
}
JSONArray array = (JSONArray) obj;
Iterator<?> iter = array.iterator();
while (iter.hasNext()) {
Object o = iter.next();
if (!(o instanceof JSONObject)) {
if (verbose) {
console.sendMessage("allowlist: array element is not an object");
}
continue;
}
JSONObject element = (JSONObject) o;
o = element.get("uuid");
if (o == null) {
if (verbose) {
console.sendMessage("allowlist: missing uuid attribute");
}
continue;
}
if (!(o instanceof String)) {
if (verbose) {
console.sendMessage("allowlist: uuid is not a string");
}
continue;
}
String uuidStr = (String) o;
UUID allowedUuid = UUID.fromString(uuidStr);
if (uuid.equals(allowedUuid)) {
if(verbose) {
console.sendMessage(String.format("allowlist: %s allowed", uuid.toString()));
}
return true;
}
}
} catch (IllegalArgumentException e) {
if (verbose) {
console.sendMessage(e.toString());
}
return false;
} catch (FileNotFoundException e) {
if (verbose) {
console.sendMessage(String.format("allowlist: no allowlist: %s allowed", uuid.toString()));
}
return true;
} catch (IOException e) {
if (verbose) {
console.sendMessage(String.format("allowlist: %s", e.toString()));
}
return false;
} catch (ParseException e) {
if (verbose) {
console.sendMessage(String.format(" allowlist: parse: %s", e.toString()));
}
return false;
}
if (verbose) {
console.sendMessage(String.format("allowlist: %s is not allowed", uuid.toString()));
}
return false;
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void run() { public void run() {
@ -553,7 +470,7 @@ public class ClientConnection extends Thread {
UUID uuid = isBungeecord || isBungeeGuard ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); UUID uuid = isBungeecord || isBungeeGuard ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
if (!uuidAllowed(uuid, !properties.isReducedDebugInfo())) { if (!Limbo.getInstance().uuidIsAllowed(uuid)) {
disconnectDuringLogin(TextComponent.fromLegacyText("You are not invited to this server")); disconnectDuringLogin(TextComponent.fromLegacyText("You are not invited to this server"));
break; break;
} }