Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val sharedGroup = "com.projectcitybuild.pcbridge"
val sharedVersion = "6.9.0"
val sharedVersion = "6.10.0"

group = sharedGroup
version = sharedVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ internal interface PCBRequest {
@Field(value = "uuid") uuid: String,
): HttpOpElevation

@POST("v3/server/audit/command/op")
@FormUrlEncoded
suspend fun auditCommandOp(
@Field(value = "command") command: String,
@Field(value = "actor") actor: String,
@Field(value = "ip") ip: String,
@Field(value = "meta") meta: String?,
)

/**
* Begins registration of a PCB account linked to the current
* Minecraft player
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,20 @@ class OpElevateHttpService(
retrofit.pcb().opRevoke(playerUUID.toString())
}
}

suspend fun audit(
command: String,
actor: String,
ip: String,
meta: String?,
) = withContext(Dispatchers.IO) {
responseParser.parse {
retrofit.pcb().auditCommandOp(
command = command,
actor = actor,
ip = ip,
meta = meta,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import com.projectcitybuild.pcbridge.paper.features.maintenance.hooks.listener.M
import com.projectcitybuild.pcbridge.paper.features.maintenance.hooks.middleware.MaintenanceConnectionMiddleware
import com.projectcitybuild.pcbridge.paper.features.moderate.hooks.commands.KickCommand
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.commands.PimCommand
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpAuditingListener
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpClearListener
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpDialogListener
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpRestoreListener
Expand Down Expand Up @@ -213,6 +214,7 @@ class PluginLifecycle : KoinComponent {
get<InvisFrameListener>(),
get<ItemTextListener>(),
get<MaintenanceReminderListener>(),
get<OpAuditingListener>(),
get<OpClearListener>(),
get<OpDialogListener>(),
get<OpRestoreListener>(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.projectcitybuild.pcbridge.paper.core.support.spigot

import kotlinx.serialization.Serializable
import org.bukkit.Location
import org.bukkit.World

@Serializable
data class SerializableLocation(
val worldId: String,
val worldName: String,
val x: Double,
val y: Double,
val z: Double,
val yaw: Float,
val pitch: Float,
) {
companion object {
fun fromLocation(location: Location, world: World) = SerializableLocation(
worldId = world.uid.toString(),
worldName = world.name,
x = location.x,
y = location.y,
z = location.z,
yaw = location.yaw,
pitch = location.pitch,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.projectcitybuild.pcbridge.paper.features.pim

import com.projectcitybuild.pcbridge.http.pcb.PCBHttp
import com.projectcitybuild.pcbridge.paper.features.pim.domain.repositories.OpAuditRepository
import com.projectcitybuild.pcbridge.paper.features.pim.domain.repositories.OpElevationRepository
import com.projectcitybuild.pcbridge.paper.features.pim.domain.services.OpElevationScheduler
import com.projectcitybuild.pcbridge.paper.features.pim.domain.services.OpElevationService
Expand All @@ -9,6 +10,7 @@ import com.projectcitybuild.pcbridge.paper.features.pim.hooks.commands.op.OpRevo
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.commands.op.OpGrantCommand
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.commands.op.OpStatusCommand
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.commands.roles.RolesDebugCommand
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpAuditingListener
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpClearListener
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpDialogListener
import com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener.OpRestoreListener
Expand Down Expand Up @@ -40,6 +42,12 @@ val pimModule = module {
VanillaOpInterceptListener()
}

factory {
OpAuditingListener(
opAuditRepository = get(),
)
}

factory {
PimCommand(
opGrantCommand = get(),
Expand Down Expand Up @@ -96,6 +104,13 @@ val pimModule = module {
)
}

factory {
OpAuditRepository(
server = get(),
opElevateHttpService = get<PCBHttp>().opElevate,
)
}

single {
OpElevationScheduler(
timer = get(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.projectcitybuild.pcbridge.paper.features.pim.domain.repositories

import com.projectcitybuild.pcbridge.http.pcb.services.OpElevateHttpService
import net.kyori.adventure.sound.SoundStop.source
import org.bukkit.Server

class OpAuditRepository(
private val server: Server,
private val opElevateHttpService: OpElevateHttpService,
) {
suspend fun auditCommand(command: String, actor: Actor) {
opElevateHttpService.audit(
command = command,
actor = actor.name,
ip = server.ip(),
meta = actor.meta,
)
}

sealed class Actor(val name: String, val meta: String? = null) {
object Rcon: Actor(
name = "rcon",
)
object Console: Actor(
name = "console",
)
data class CommandBlock(val blockMeta: String?): Actor(
name = "command_block",
meta = blockMeta,
)
object Unknown: Actor(
name = "unknown",
)
}
}

private fun Server.ip(): String {
if (ip.isEmpty()) return "127.0.0.1"
return ip
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.projectcitybuild.pcbridge.paper.features.pim.hooks.listener

import com.projectcitybuild.pcbridge.paper.architecture.listeners.scoped
import com.projectcitybuild.pcbridge.paper.core.support.spigot.SerializableLocation
import com.projectcitybuild.pcbridge.paper.features.pim.domain.repositories.OpAuditRepository
import com.projectcitybuild.pcbridge.paper.features.pim.pimTracer
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson
import org.bukkit.command.BlockCommandSender
import org.bukkit.command.CommandSender
import org.bukkit.command.ConsoleCommandSender
import org.bukkit.command.RemoteConsoleCommandSender
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.server.ServerCommandEvent

class OpAuditingListener(
private val opAuditRepository: OpAuditRepository,
): Listener {
/**
* Monitors /op /deop usage by console, command blocks or rcon,
* and logs their usage for auditing purposes
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
suspend fun onServerCommandEvent(
event: ServerCommandEvent,
) = event.scoped(pimTracer, this::class.java) {
val command = event.command.lowercase() // No leading "/"

if (!command.startsWith("op ") && !command.startsWith("deop ")) {
return@scoped
}
val args = command.split(" ")
if (args.size < 2) return@scoped

opAuditRepository.auditCommand(
command = command,
actor = event.sender.actor()
)
}
}

private fun CommandSender.actor() = when (this) {
is RemoteConsoleCommandSender -> OpAuditRepository.Actor.Rcon
is ConsoleCommandSender -> OpAuditRepository.Actor.Console
is BlockCommandSender -> OpAuditRepository.Actor.CommandBlock(
blockMeta = gson().serializer().toJson(
SerializableLocation.fromLocation(block.location, block.world)
)
)
else -> OpAuditRepository.Actor.Unknown
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerCommandPreprocessEvent

class VanillaOpInterceptListener: Listener {
@EventHandler(priority = EventPriority.HIGHEST)
/**
* Intercepts /op /deop usage by players and cancels them
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
fun onPlayerCommandPreprocess(
event: PlayerCommandPreprocessEvent,
) = event.scopedSync(pimTracer, this::class.java) {
Expand Down
2 changes: 1 addition & 1 deletion pcbridge-paper/src/main/resources/paper-plugin.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: PCBridge
website: https://github.com/projectcitybuild/PCBridge
version: 6.9.0
version: 6.10.0
api-version: 1.21.10
main: com.projectcitybuild.pcbridge.paper.Plugin
bootstrapper: com.projectcitybuild.pcbridge.paper.PaperBootstrap
Expand Down