Skip to content
Merged
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2101.1.5]

### Changed
* FTB Ranks commands which change rank state in any way now require permission level 4 (server owner) instead of 2
* In particular this means that running ranks commands from command blocks or signs is no longer possible
* For SSP, open-to-lan behaviour is now safer: players joining a published server no longer have access to ranks commands

### Fixed
* The `/ftbranks` node command no longer accepts `name` and `power` as node names, since they're reserved
* Fixed rank player membership data not always getting marked as save-needed on change

## [2101.1.4]

### Fixed
Expand Down
37 changes: 31 additions & 6 deletions common/src/main/java/dev/ftb/mods/ftbranks/FTBRanksCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import dev.ftb.mods.ftblibrary.snbt.SNBT;
import dev.ftb.mods.ftbranks.api.*;
import dev.ftb.mods.ftbranks.impl.FTBRanksAPIImpl;
import dev.ftb.mods.ftbranks.impl.RankManagerImpl;
import dev.ftb.mods.ftbranks.impl.condition.DefaultCondition;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandBuildContext;
Expand All @@ -39,34 +40,54 @@ public class FTBRanksCommands {
(object) -> Component.literal("Unknown rank: " + object.toString())
);

public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext, Commands.CommandSelection selection) {
private static boolean isCommandSourceAllowed(CommandSourceStack source) {
// source.getServer() *can* return null: https://github.com/FTBTeam/FTB-Mods-Issues/issues/766
//noinspection ConstantValue
if (source.getServer() == null) {
return false;
}

// from console, or owner of SSP world (incl open to LAN), or has GM perm level or better
return source.getPlayer() == null
|| source.getServer().isSingleplayerOwner(source.getPlayer().getGameProfile())
|| source.getPlayer().hasPermissions(Commands.LEVEL_GAMEMASTERS);
}

private static boolean isServerOp(CommandSourceStack sourceStack) {
return sourceStack.hasPermission(Commands.LEVEL_OWNERS);
}

public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext, Commands.CommandSelection selection) {
dispatcher.register(Commands.literal("ftbranks")
.requires(source -> source.getServer() != null && source.getServer().isSingleplayer() || source.hasPermission(2))
.requires(FTBRanksCommands::isCommandSourceAllowed)
.then(Commands.literal("reload")
.requires(FTBRanksCommands::isServerOp)
.executes(context -> reloadRanks(context.getSource()))
)
.then(Commands.literal("refresh_readme")
.requires(FTBRanksCommands::isServerOp)
.executes(context -> refreshReadme(context.getSource()))
)
.then(Commands.literal("list_all_ranks")
.executes(context -> listAllRanks(context.getSource()))
)
.then(Commands.literal("create")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("name", StringArgumentType.word())
.then(Commands.argument("power", IntegerArgumentType.integer(1))
.executes(context -> createRank(context.getSource(), StringArgumentType.getString(context, "name"), IntegerArgumentType.getInteger(context,"power"))))
.executes(context -> createRank(context.getSource(), StringArgumentType.getString(context, "name"), 1))
)
)
.then(Commands.literal("delete")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("rank", StringArgumentType.word())
.suggests((context, builder) -> suggestRanks(builder))
.executes(context -> deleteRank(context.getSource(), StringArgumentType.getString(context, "rank")))
)
)
.then(Commands.literal("add")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("players", GameProfileArgument.gameProfile())
.then(Commands.argument("rank", StringArgumentType.word())
.suggests((context, builder) -> suggestRanks(builder))
Expand All @@ -75,6 +96,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher, Co
)
)
.then(Commands.literal("remove")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("players", GameProfileArgument.gameProfile())
.then(Commands.argument("rank", StringArgumentType.word())
.suggests((context, builder) -> suggestRanks(builder))
Expand All @@ -95,6 +117,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher, Co
)
.then(Commands.literal("node")
.then(Commands.literal("add")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("rank", StringArgumentType.word())
.suggests((context, builder) -> suggestRanks(builder))
.then(Commands.argument("node", StringArgumentType.word())
Expand All @@ -105,6 +128,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher, Co
)
)
.then(Commands.literal("remove")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("rank", StringArgumentType.word())
.suggests((context, builder) -> suggestRanks(builder))
.then(Commands.argument("node", StringArgumentType.word())
Expand All @@ -120,6 +144,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher, Co
)
)
.then(Commands.literal("condition")
.requires(FTBRanksCommands::isServerOp)
.then(Commands.argument("rank", StringArgumentType.word())
.suggests((context, builder) -> suggestRanks(builder))
.then(Commands.argument("value", StringArgumentType.greedyString())
Expand Down Expand Up @@ -149,7 +174,7 @@ private static String normalizeRankName(String name) {

private static int reloadRanks(CommandSourceStack source) {
try {
FTBRanksAPIImpl.manager.reload();
((RankManagerImpl) FTBRanksAPIImpl.getInstance().getManager()).reload();
source.sendSuccess(() -> Component.literal("Ranks reloaded from disk!"), true);

for (ServerPlayer p : source.getServer().getPlayerList().getPlayers()) {
Expand All @@ -166,7 +191,7 @@ private static int reloadRanks(CommandSourceStack source) {

private static int refreshReadme(CommandSourceStack source) {
try {
FTBRanksAPIImpl.manager.refreshReadme();
((RankManagerImpl) FTBRanksAPIImpl.getInstance().getManager()).refreshReadme();
} catch (IOException ex) {
ex.printStackTrace();
}
Expand All @@ -191,7 +216,7 @@ private static Component makeRankNameClicky(Rank rank) {
private static int listAllRanks(CommandSourceStack source) {
source.sendSuccess(() -> Component.literal("Ranks:"), false);

for (Rank rank : FTBRanksAPIImpl.manager.getAllRanks()) {
for (Rank rank : FTBRanksAPIImpl.getInstance().getManager().getAllRanks()) {
source.sendSuccess(() -> Component.literal("- ").append(makeRankNameClicky(rank)), false);
}

Expand Down Expand Up @@ -242,7 +267,7 @@ private static int removeRank(CommandSourceStack source, Collection<GameProfile>
private static int listRanksOf(CommandSourceStack source, ServerPlayer player) {
source.sendSuccess(() -> Component.literal(String.format("Ranks added to player '%s':", player.getGameProfile().getName())), false);

for (Rank rank : FTBRanksAPIImpl.manager.getAllRanks()) {
for (Rank rank : FTBRanksAPIImpl.getInstance().getManager().getAllRanks()) {
if (rank.isActive(player)) {
source.sendSuccess(() -> Component.literal("- ").append(makeRankNameClicky(rank)), false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/**
* Top-level API object
*/
@ApiStatus.NonExtendable
public abstract class FTBRanksAPI {
private static FTBRanksAPI instance;

Expand Down Expand Up @@ -56,7 +57,7 @@ public static PermissionValue getPermissionValue(ServerPlayer player, String nod
*/
@ApiStatus.Internal
public static void setup(FTBRanksAPI theInstance) {
if (instance != null || !theInstance.getClass().getName().startsWith("dev.ftb.mods.ftbranks")) {
if (instance != null || !theInstance.getClass().getPackageName().equals("dev.ftb.mods.ftbranks.impl")) {
throw new IllegalStateException("don't do this");
}
instance = theInstance;
Expand All @@ -66,5 +67,5 @@ public static void setup(FTBRanksAPI theInstance) {
* Get the manager
* @return the manager
*/
protected abstract RankManager getManager();
public abstract RankManager getManager();
}
2 changes: 2 additions & 0 deletions common/src/main/java/dev/ftb/mods/ftbranks/api/Rank.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.mojang.authlib.GameProfile;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -12,6 +13,7 @@
* players the rank applies to. A rank also has a "power" level; the highest-powered rank will apply to a player if
* more than one rank is applicable.
*/
@ApiStatus.NonExtendable
public interface Rank {
/**
* Convenience method to get the rank manager.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -15,6 +16,7 @@
/**
* Top-level manager object.
*/
@ApiStatus.NonExtendable
public interface RankManager {
/**
* Get all the known ranks, ordered by rank power, from highest to lowest. The rank's power determines which rank
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
import org.apache.commons.lang3.math.NumberUtils;

public class FTBRanksAPIImpl extends FTBRanksAPI {
public static RankManagerImpl manager;
private static RankManagerImpl manager;

@Override
protected RankManager getManager() {
public RankManager getManager() {
return manager;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ public class PlayerRankData {
private final UUID playerId;
private final String name;
private final Map<Rank, Instant> added;
private final Map<String, PermissionValue> permissions;

public PlayerRankData(RankManagerImpl manager, UUID playerId, String name) {
this.manager = manager;
this.playerId = playerId;
this.name = name;
this.added = new LinkedHashMap<>();
this.permissions = new LinkedHashMap<>();
}

public UUID getPlayerId() {
Expand Down Expand Up @@ -63,9 +61,15 @@ public int hashCode() {
return Objects.hash(playerId);
}

/**
* Player-specific permission nodes have never worked correctly and will be removed.
* @param node the node
* @return always returns MISSING
*/
@Deprecated(forRemoval = true)
@NotNull
public PermissionValue getPermission(String node) {
return permissions.getOrDefault(node, PermissionValue.MISSING);
return PermissionValue.MISSING;
}

SNBTCompoundTag writeSNBT() {
Expand All @@ -83,11 +87,6 @@ SNBTCompoundTag writeSNBT() {
res.put("ranks", ranksTag);
}

SNBTCompoundTag permTag = RankManagerImpl.writePermissions(permissions, new SNBTCompoundTag());
if (!permTag.isEmpty()) {
res.put("permissions", permTag);
}

return res;
}

Expand All @@ -106,17 +105,6 @@ static PlayerRankData fromSNBT(RankManagerImpl manager, UUID playerId, SNBTCompo
}
}

SNBTCompoundTag permTag = tag.getCompound("permissions");
for (String permKey : permTag.getAllKeys()) {
while (permKey.endsWith(".*")) {
permKey = permKey.substring(0, permKey.length() - 2);
manager.markPlayerDataDirty();
}
if (!permKey.isEmpty()) {
data.permissions.put(playerId.toString(), RankManagerImpl.ofTag(permTag, permKey));
}
}

return data;
}
}
Loading
Loading