1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-04 23:40:00 +00:00

Several command permission fixes

- Attach permission checks to the first argument (so the literal
   command name) rather than the last argument. This fixes commands
   showing up when they shouldn't.

 - HelpingArgumentBuilder now inherits permissions of its leaf nodes.
   This only really impacts the "track" subcommand.

 - Don't autocomplete the computer selector for the "queue" subcommand.
   As everyone has permission for this command, it's possible to find
   all computer ids and labels in the world.

   I'm in mixed minds about this, but don't think this is an exploit -
   computer ids/labels are sent to in-range players so shouldn't be
   considered secret - but worth patching none-the-less.
This commit is contained in:
Jonathan Coates 2023-07-06 23:41:02 +01:00
parent f629831b12
commit 7436447a6e
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
4 changed files with 79 additions and 18 deletions

View File

@ -7,10 +7,13 @@ package dan200.computercraft.shared.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
@ -201,7 +204,10 @@ public final class CommandComputerCraft
.then( command( "queue" )
.requires( UserLevel.ANYONE )
.arg( "computer", manyComputers() )
.arg(
RequiredArgumentBuilder.<CommandSource, ComputersArgumentType.ComputersSupplier>argument( "computer", manyComputers() )
.suggests( ( context, builder ) -> Suggestions.empty() )
)
.argManyValue( "args", StringArgumentType.string(), Collections.emptyList() )
.executes( ( ctx, args ) -> {
Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" );

View File

@ -61,12 +61,36 @@ public enum UserLevel implements Predicate<CommandSource>
return source.hasPermission( toLevel() );
}
/**
* Take the union of two {@link UserLevel}s.
* <p>
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a b).test(s)}.
*
* @param left The first user level to take the union of.
* @param right The second user level to take the union of.
* @return The union of two levels.
*/
public static UserLevel union( UserLevel left, UserLevel right )
{
if( left == right ) return left;
// x ANYONE = ANYONE
if( left == ANYONE || right == ANYONE ) return ANYONE;
// x OWNER = OWNER
if( left == OWNER ) return right;
if( right == OWNER ) return left;
// At this point, we have x != y and x, y { OP, OWNER_OP }.
return OWNER_OP;
}
private static boolean isOwner( CommandSource source )
{
MinecraftServer server = source.getServer();
Entity sender = source.getEntity();
return server.isDedicatedServer()
? source.getEntity() == null && source.hasPermission( 4 ) && source.getTextName().equals( "Server" )
: sender instanceof PlayerEntity && ((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() );
: sender instanceof PlayerEntity && server.isSingleplayerOwner( ((PlayerEntity) sender).getGameProfile() );
}
}

View File

@ -52,10 +52,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
return this;
}
public CommandBuilder<S> arg( ArgumentBuilder<S, ?> arg )
{
args.add( arg );
return this;
}
public CommandBuilder<S> arg( String name, ArgumentType<?> type )
{
args.add( RequiredArgumentBuilder.argument( name, type ) );
return this;
return arg( RequiredArgumentBuilder.argument( name, type ) );
}
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue( String name, ArgumentType<T> type, List<T> empty )
@ -84,7 +89,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
return command -> {
// The node for no arguments
ArgumentBuilder<S, ?> tail = tail( ctx -> command.run( ctx, empty.get() ) );
ArgumentBuilder<S, ?> tail = setupTail( ctx -> command.run( ctx, empty.get() ) );
// The node for one or more arguments
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
@ -93,7 +98,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
// Chain all of them together!
tail.then( moreArg );
return link( tail );
return buildTail( tail );
};
}
@ -106,22 +111,18 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
@Override
public CommandNode<S> executes( Command<S> command )
{
if( args.isEmpty() ) throw new IllegalStateException( "Cannot have empty arg chain builder" );
return link( tail( command ) );
return buildTail( setupTail( command ) );
}
private ArgumentBuilder<S, ?> tail( Command<S> command )
private ArgumentBuilder<S, ?> setupTail( Command<S> command )
{
ArgumentBuilder<S, ?> defaultTail = args.get( args.size() - 1 );
defaultTail.executes( command );
if( requires != null ) defaultTail.requires( requires );
return defaultTail;
return args.get( args.size() - 1 ).executes( command );
}
private CommandNode<S> link( ArgumentBuilder<S, ?> tail )
private CommandNode<S> buildTail( ArgumentBuilder<S, ?> tail )
{
for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail );
if( requires != null ) tail.requires( requires );
return tail.build();
}
}

View File

@ -12,6 +12,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.shared.command.UserLevel;
import net.minecraft.command.CommandSource;
import net.minecraft.util.text.IFormattableTextComponent;
import net.minecraft.util.text.ITextComponent;
@ -22,6 +23,10 @@ import net.minecraft.util.text.event.ClickEvent;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
@ -44,6 +49,33 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
return new HelpingArgumentBuilder( literal );
}
@Override
public LiteralArgumentBuilder<CommandSource> requires( Predicate<CommandSource> requirement )
{
throw new IllegalStateException( "Cannot use requires on a HelpingArgumentBuilder" );
}
@Override
public Predicate<CommandSource> getRequirement()
{
// The requirement of this node is the union of all child's requirements.
List<Predicate<CommandSource>> requirements = Stream.concat(
children.stream().map( ArgumentBuilder::getRequirement ),
getArguments().stream().map( CommandNode::getRequirement )
).collect( Collectors.toList() );
// If all requirements are a UserLevel, take the union of those instead.
UserLevel userLevel = UserLevel.OWNER;
for( Predicate<CommandSource> requirement : requirements )
{
if( !(requirement instanceof UserLevel) ) return x -> requirements.stream().anyMatch( y -> y.test( x ) );
userLevel = UserLevel.union( userLevel, (UserLevel) requirement );
}
return userLevel;
}
@Override
public LiteralArgumentBuilder<CommandSource> executes( final Command<CommandSource> command )
{
@ -99,9 +131,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
helpCommand.node = node;
// Set up a /... help command
LiteralArgumentBuilder<CommandSource> helpNode = LiteralArgumentBuilder.<CommandSource>literal( "help" )
.requires( x -> getArguments().stream().anyMatch( y -> y.getRequirement().test( x ) ) )
.executes( helpCommand );
LiteralArgumentBuilder<CommandSource> helpNode = LiteralArgumentBuilder.<CommandSource>literal( "help" ).executes( helpCommand );
// Add all normal command children to this and the help node
for( CommandNode<CommandSource> child : getArguments() )