Separated MC-specific command metadata
Implementing permission handling for MC
This commit is contained in:
parent
be5f9ded60
commit
af7d097f9b
7 changed files with 97 additions and 55 deletions
|
@ -5,7 +5,6 @@ import buttondevteam.core.ComponentManager
|
||||||
import buttondevteam.lib.TBMCCoreAPI
|
import buttondevteam.lib.TBMCCoreAPI
|
||||||
import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException
|
import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException
|
||||||
import buttondevteam.lib.chat.ICommand2MC
|
import buttondevteam.lib.chat.ICommand2MC
|
||||||
import lombok.Getter
|
|
||||||
import org.bukkit.configuration.ConfigurationSection
|
import org.bukkit.configuration.ConfigurationSection
|
||||||
import org.bukkit.event.Listener
|
import org.bukkit.event.Listener
|
||||||
import org.bukkit.plugin.java.JavaPlugin
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
@ -24,7 +23,6 @@ abstract class Component<TP : JavaPlugin?> {
|
||||||
|
|
||||||
val config = IHaveConfig(null)
|
val config = IHaveConfig(null)
|
||||||
|
|
||||||
@Getter
|
|
||||||
private val data //TODO
|
private val data //TODO
|
||||||
: IHaveConfig? = null
|
: IHaveConfig? = null
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package buttondevteam.lib.architecture
|
||||||
|
|
||||||
import buttondevteam.core.MainPlugin
|
import buttondevteam.core.MainPlugin
|
||||||
import buttondevteam.lib.ChromaUtils
|
import buttondevteam.lib.ChromaUtils
|
||||||
import lombok.*
|
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.configuration.Configuration
|
import org.bukkit.configuration.Configuration
|
||||||
import org.bukkit.scheduler.BukkitTask
|
import org.bukkit.scheduler.BukkitTask
|
||||||
|
|
|
@ -12,7 +12,6 @@ import com.mojang.brigadier.context.CommandContext
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||||
import com.mojang.brigadier.tree.CommandNode
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode
|
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||||
import lombok.RequiredArgsConstructor
|
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
@ -24,8 +23,17 @@ import java.util.stream.Collectors
|
||||||
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
||||||
* The args may be null if the conversion failed and it's optional.
|
* The args may be null if the conversion failed and it's optional.
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
/**
|
||||||
|
* The first character in the command line that shows that it's a command.
|
||||||
|
*/
|
||||||
|
private val commandChar: Char,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the command's actual code has to be run on the primary thread.
|
||||||
|
*/
|
||||||
|
private val runOnPrimaryThread: Boolean
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* Parameters annotated with this receive all the remaining arguments
|
* Parameters annotated with this receive all the remaining arguments
|
||||||
*/
|
*/
|
||||||
|
@ -44,16 +52,10 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
*/
|
*/
|
||||||
val helpText: Array<String> = [],
|
val helpText: Array<String> = [],
|
||||||
/**
|
/**
|
||||||
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
|
* Aliases for the subcommand that can be used to invoke it in addition to the method name.
|
||||||
* Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this.
|
|
||||||
*/
|
*/
|
||||||
val permGroup: String = "", val aliases: Array<String> = []) {
|
val aliases: Array<String> = [] // TODO
|
||||||
companion object {
|
) {
|
||||||
/**
|
|
||||||
* Allowed for OPs only by default
|
|
||||||
*/
|
|
||||||
const val MOD_GROUP = "mod"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
@ -66,16 +68,6 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
|
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
|
||||||
private val dispatcher = CommandDispatcher<TP>()
|
private val dispatcher = CommandDispatcher<TP>()
|
||||||
|
|
||||||
/**
|
|
||||||
* The first character in the command line that shows that it's a command.
|
|
||||||
*/
|
|
||||||
private val commandChar = 0.toChar()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the command's actual code has to be run on the primary thread.
|
|
||||||
*/
|
|
||||||
private val runOnPrimaryThread = false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a param converter that obtains a specific object from a string parameter.
|
* Adds a param converter that obtains a specific object from a string parameter.
|
||||||
* The converter may return null.
|
* The converter may return null.
|
||||||
|
@ -96,13 +88,17 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
return false // Unknown command
|
return false // Unknown command
|
||||||
}
|
}
|
||||||
//Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
|
//Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) {
|
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) { _ ->
|
||||||
try {
|
try {
|
||||||
dispatcher.execute(results)
|
dispatcher.execute(results)
|
||||||
} catch (e: CommandSyntaxException) {
|
} catch (e: CommandSyntaxException) {
|
||||||
sender.sendMessage(e.message)
|
sender.sendMessage(e.message)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, e, MainPlugin.Instance)
|
TBMCCoreAPI.SendException(
|
||||||
|
"Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline,
|
||||||
|
e,
|
||||||
|
MainPlugin.Instance
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true //We found a method
|
return true //We found a method
|
||||||
|
@ -342,7 +338,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
invokeCommand.run();*/return 0
|
invokeCommand.run();*/return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun hasPermission(sender: TP, command: TC, subcommand: Method?): Boolean
|
abstract fun hasPermission(context: CommandContext<TP>): Boolean
|
||||||
val commandsText: Array<String> get() = commandHelp.toTypedArray()
|
val commandsText: Array<String> get() = commandHelp.toTypedArray()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -393,4 +389,11 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
|
root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
|
||||||
for (child in root.children) unregisterCommandIf(condition, child.core())
|
for (child in root.children) unregisterCommandIf(condition, child.core())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all subcommands of the specified command. Only returns executable nodes.
|
||||||
|
*/
|
||||||
|
fun getSubcommands(mainCommand: LiteralCommandNode<TP>): List<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>> {
|
||||||
|
return dispatcher.root.children.mapNotNull { it.coreExecutable<TP, TC>() } // TODO: Needs more depth
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,6 +5,8 @@ import buttondevteam.lib.TBMCCoreAPI
|
||||||
import buttondevteam.lib.architecture.ButtonPlugin
|
import buttondevteam.lib.architecture.ButtonPlugin
|
||||||
import buttondevteam.lib.architecture.Component
|
import buttondevteam.lib.architecture.Component
|
||||||
import buttondevteam.lib.chat.commands.CommandUtils
|
import buttondevteam.lib.chat.commands.CommandUtils
|
||||||
|
import buttondevteam.lib.chat.commands.CommandUtils.subcommandPath
|
||||||
|
import buttondevteam.lib.chat.commands.MCCommandSettings
|
||||||
import buttondevteam.lib.chat.commands.SubcommandData
|
import buttondevteam.lib.chat.commands.SubcommandData
|
||||||
import buttondevteam.lib.player.ChromaGamerBase
|
import buttondevteam.lib.player.ChromaGamerBase
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
@ -57,44 +59,52 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
||||||
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
|
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
|
||||||
Bukkit.getPluginManager().addPermission(Permission(perm,
|
Bukkit.getPluginManager().addPermission(Permission(perm,
|
||||||
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
||||||
for (method in command.javaClass.methods) {
|
for (node in getSubcommands(commandNode)) {
|
||||||
if (!method.isAnnotationPresent(Subcommand::class.java)) continue
|
|
||||||
val path = CommandUtils.getCommandPath(method.name, '.')
|
|
||||||
if (path.length > 0) {
|
if (path.length > 0) {
|
||||||
val subperm = perm + path
|
val subperm = perm + path
|
||||||
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
|
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
|
||||||
Bukkit.getPluginManager().addPermission(Permission(subperm,
|
Bukkit.getPluginManager().addPermission(
|
||||||
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
Permission(
|
||||||
|
subperm,
|
||||||
|
PermissionDefault.TRUE
|
||||||
|
)
|
||||||
|
) //Allow commands by default, it will check mod-only
|
||||||
}
|
}
|
||||||
val pg = permGroup(command, method)
|
val pg = permGroup(node.data)
|
||||||
if (pg.length == 0) continue
|
if (pg.isEmpty()) continue
|
||||||
val permGroup = "chroma.$pg"
|
val permGroup = "chroma.$pg"
|
||||||
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
|
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
|
||||||
Bukkit.getPluginManager().addPermission(Permission(permGroup,
|
Bukkit.getPluginManager().addPermission(
|
||||||
PermissionDefault.OP)) //Do not allow any commands that belong to a group
|
Permission(
|
||||||
|
permGroup,
|
||||||
|
PermissionDefault.OP
|
||||||
|
)
|
||||||
|
) //Do not allow any commands that belong to a group
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasPermission(sender: Command2MCSender, command: ICommand2MC, method: Method): Boolean {
|
override fun hasPermission(context: CommandContext<Command2MCSender>): Boolean {
|
||||||
return hasPermission(sender.sender, command, method)
|
return hasPermission(context.source.sender, context.subcommandPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasPermission(sender: CommandSender, command: ICommand2MC?, method: Method): Boolean {
|
fun hasPermission(sender: CommandSender, path: String): Boolean {
|
||||||
if (sender is ConsoleCommandSender) return true //Always allow the console
|
if (sender is ConsoleCommandSender) return true //Always allow the console
|
||||||
if (command == null) return true //Allow viewing the command - it doesn't do anything anyway
|
|
||||||
var pg: String
|
var pg: String
|
||||||
var p = true
|
var p = true
|
||||||
val cmdperm = "chroma.command." + command.commandPath.replace(' ', '.')
|
val cmdperm = "chroma.command.$path"
|
||||||
val path = CommandUtils.getCommandPath(method.name, '.')
|
// TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined
|
||||||
val perms = arrayOf(
|
val perms = arrayOf(
|
||||||
if (path.length > 0) cmdperm + path else null,
|
cmdperm + path,
|
||||||
cmdperm,
|
|
||||||
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
|
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
|
||||||
)
|
)
|
||||||
for (perm in perms) {
|
for (perm in perms) {
|
||||||
if (perm != null) {
|
if (perm != null) {
|
||||||
if (p) { //Use OfflinePlayer to avoid fetching player data
|
if (p) { //Use OfflinePlayer to avoid fetching player data
|
||||||
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(if (sender is Player) sender.location.world.name else null, sender as OfflinePlayer, perm) else false //Use sender's method
|
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(
|
||||||
|
if (sender is Player) sender.location.world.name else null,
|
||||||
|
sender as OfflinePlayer,
|
||||||
|
perm
|
||||||
|
) else false //Use sender's method
|
||||||
if (!p) p = sender.hasPermission(perm)
|
if (!p) p = sender.hasPermission(perm)
|
||||||
} else break //If any of the permissions aren't granted then don't allow
|
} else break //If any of the permissions aren't granted then don't allow
|
||||||
}
|
}
|
||||||
|
@ -108,14 +118,11 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
||||||
* @param method The subcommand to check
|
* @param method The subcommand to check
|
||||||
* @return The permission group for the subcommand or empty string
|
* @return The permission group for the subcommand or empty string
|
||||||
*/
|
*/
|
||||||
private fun permGroup(command: ICommand2MC, method: Method?): String {
|
private fun permGroup(data: SubcommandData<ICommand2MC, Command2MCSender>): String {
|
||||||
if (method != null) {
|
val group = data.annotations.filterIsInstance<MCCommandSettings>().map {
|
||||||
val sc = method.getAnnotation(Subcommand::class.java)
|
if (it.permGroup.isEmpty() && it.modOnly) MCCommandSettings.MOD_GROUP else ""
|
||||||
if (sc != null && sc.permGroup().length > 0) {
|
}.firstOrNull()
|
||||||
return sc.permGroup()
|
return group ?: ""
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (getAnnForValue(command.javaClass, CommandClass::class.java, Function { obj: CommandClass -> obj.modOnly() }, false)) Subcommand.MOD_GROUP else getAnnForValue(command.javaClass, CommandClass::class.java, Function<CommandClass, String> { obj: CommandClass -> obj.permGroup() }, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,7 @@ package buttondevteam.lib.chat.commands
|
||||||
import buttondevteam.lib.chat.Command2Sender
|
import buttondevteam.lib.chat.Command2Sender
|
||||||
import buttondevteam.lib.chat.CoreCommandNode
|
import buttondevteam.lib.chat.CoreCommandNode
|
||||||
import buttondevteam.lib.chat.ICommand2
|
import buttondevteam.lib.chat.ICommand2
|
||||||
|
import com.mojang.brigadier.context.CommandContext
|
||||||
import com.mojang.brigadier.tree.CommandNode
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -34,4 +35,9 @@ object CommandUtils {
|
||||||
val ret = core<TP, TC, NoOpSubcommandData>()
|
val ret = core<TP, TC, NoOpSubcommandData>()
|
||||||
return if (ret.data is SubcommandData<*, *>) ret.core() else null
|
return if (ret.data is SubcommandData<*, *>) ret.core() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val <TP : Command2Sender> CommandContext<TP>.subcommandPath
|
||||||
|
get(): String {
|
||||||
|
TODO("Return command path")
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package buttondevteam.lib.chat.commands
|
||||||
|
|
||||||
|
import java.lang.annotation.Inherited
|
||||||
|
|
||||||
|
@Inherited
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
annotation class MCCommandSettings(
|
||||||
|
/**
|
||||||
|
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
|
||||||
|
* Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this.
|
||||||
|
*/
|
||||||
|
val permGroup: String = "",
|
||||||
|
/**
|
||||||
|
* Whether the (sub)command is mod only. This means it requires the chroma.mod permission.
|
||||||
|
* This is just a shorthand for providing MOD_GROUP for permGroup.
|
||||||
|
*/
|
||||||
|
val modOnly: Boolean = false
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Allowed for OPs only by default
|
||||||
|
*/
|
||||||
|
const val MOD_GROUP = "mod"
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,11 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
|
||||||
/**
|
/**
|
||||||
* A function that determines whether the user has permission to run this subcommand.
|
* A function that determines whether the user has permission to run this subcommand.
|
||||||
*/
|
*/
|
||||||
private val permissionCheck: (TP) -> Boolean
|
private val permissionCheck: (TP) -> Boolean,
|
||||||
|
/**
|
||||||
|
* All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform.
|
||||||
|
*/
|
||||||
|
val annotations: Array<Annotation>
|
||||||
) : NoOpSubcommandData(helpTextGetter) {
|
) : NoOpSubcommandData(helpTextGetter) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue