Dieser Commit ist enthalten in:
Chaoscaot 2023-11-12 22:44:31 +01:00
Ursprung dfc3fd0b3b
Commit c9013a69c6
10 geänderte Dateien mit 275 neuen und 17 gelöschten Zeilen

1
.gitignore vendored
Datei anzeigen

@ -35,3 +35,4 @@ out/
### VS Code ###
.vscode/
/config.json
/logs/

@ -1 +1 @@
Subproject commit fa049455956e40efd126082c1eef470698263abb
Subproject commit 61985fb80673b318a9a9b6bfa345b670ac0e1da9

Datei anzeigen

@ -20,6 +20,8 @@
package de.steamwar
import de.steamwar.plugins.configurePlugins
import de.steamwar.routes.ResponseUser
import de.steamwar.routes.SchematicCode
import io.ktor.server.application.*
import io.ktor.server.engine.*
import de.steamwar.routes.configureRoutes
@ -42,6 +44,12 @@ val config = Json.decodeFromStream<Config>(File("config.json").inputStream())
fun main() {
SchematicType.Normal.name().length
Thread {
while (true) {
Thread.sleep(1000 * 10)
ResponseUser.clearCache()
}
}.start()
embeddedServer(Netty, port = 1337, host = "127.0.0.1", module = Application::module)
.start(wait = true)
}

Datei anzeigen

@ -32,7 +32,7 @@ import io.ktor.server.response.*
data class SWAuthPrincipal(val token: Token, val user: SteamwarUser) : Principal
class SWAuthConfig {
public var permission: UserPerm = UserPerm.MODERATION
public var permission: UserPerm? = null
public var allowedMethods = mutableListOf<HttpMethod>()
public var userCheck: SWAuthPrincipal.(ApplicationRequest) -> Boolean = { true }
public var mustAuth: Boolean = false
@ -72,7 +72,7 @@ val SWPermissionCheck = createRouteScopedPlugin("SWAuth", ::SWAuthConfig) {
return@on
}
if (!token.user.hasPerm(permission)) {
if (permission != null && !token.user.hasPerm(permission)) {
call.respond(HttpStatusCode.Forbidden)
return@on
}

Datei anzeigen

@ -23,9 +23,6 @@ import de.steamwar.sql.Token
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.application.*
import io.ktor.server.application.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.contentnegotiation.*
@ -89,9 +86,9 @@ fun Application.configurePlugins() {
format {
val verified = it.principal<SWAuthPrincipal>()
if (verified != null) {
"User: ${verified.token.owner.userName}, Token: ${verified.token.name}, ${it.request.httpMethod.value} ${it.request.uri}, Response: ${it.response.status()?.value}"
"User (${it.request.local.remoteAddress}): ${verified.token.owner.userName}, Token: ${verified.token.name}, ${it.request.httpMethod.value} ${it.request.uri}, Response: ${it.response.status()?.value}, User-Agent: ${it.request.headers["User-Agent"] ?: "Unknown"}"
} else {
"Unauthenticated Request: ${it.request.httpMethod.value} ${it.request.uri}, Response: ${it.response.status()?.value}"
"Unauthenticated Request (${it.request.local.remoteAddress}): ${it.request.httpMethod.value} ${it.request.uri}, Response: ${it.response.status()?.value}, User-Agent: ${it.request.headers["User-Agent"] ?: "Unknown"}"
}
}
}

Datei anzeigen

@ -21,6 +21,8 @@ package de.steamwar.routes
import de.steamwar.ResponseError
import de.steamwar.data.Groups
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.SchematicType
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.UserPerm
@ -28,6 +30,7 @@ import de.steamwar.sql.loadSchematicTypes
import de.steamwar.util.fetchData
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
@ -39,8 +42,33 @@ import java.net.InetSocketAddress
data class ResponseSchematicType(val name: String, val db: String)
@Serializable
data class ResponseUser(val id: Int, val name: String, val uuid: String, val prefix: String) {
constructor(user: SteamwarUser) : this(user.id, user.userName, user.uuid.toString(), user.prefix().chatPrefix)
data class ResponseUser(val id: Int, val name: String, val uuid: String, val prefix: String, val perms: List<String>) {
constructor(user: SteamwarUser) : this(user.id, user.userName, user.uuid.toString(), user.prefix().chatPrefix, user.perms().map { it.name }) {
synchronized(cache) {
cache[id] = this
}
}
companion object {
private val cache = mutableMapOf<Int, ResponseUser>()
fun get(id: Int): ResponseUser {
synchronized(cache) {
if(cache.containsKey(id)) {
return cache[id]!!
}
val user = ResponseUser(SteamwarUser.get(id))
cache[id] = user
return user
}
}
fun clearCache() {
synchronized(cache) {
cache.clear()
}
}
}
}
fun Route.configureDataRoutes() {
@ -91,5 +119,12 @@ fun Route.configureDataRoutes() {
return@get
}
}
route("/me") {
install(SWPermissionCheck)
get {
call.respond(ResponseUser(call.principal<SWAuthPrincipal>()!!.user))
}
}
}
}

Datei anzeigen

@ -40,7 +40,7 @@ import java.time.Instant
data class ResponseEventFight(val id: Int, val spielmodus: String, val map: String, val blueTeam: ResponseTeam, val redTeam: ResponseTeam, val kampfleiter: ResponseUser, val start: Long, val ergebnis: Int, val group: String?) {
constructor(eventFight: EventFight): this(eventFight.fightID, eventFight.spielModus, eventFight.map, ResponseTeam(
Team.get(eventFight.teamBlue)), ResponseTeam(Team.get(eventFight.teamRed)),
ResponseUser(SteamwarUser.get(eventFight.kampfleiter)), eventFight.startTime.time, eventFight.ergebnis, Groups.getGroup(eventFight.fightID)?.name)
ResponseUser.get(eventFight.kampfleiter), eventFight.startTime.time, eventFight.ergebnis, Groups.getGroup(eventFight.fightID)?.name)
}
@Serializable

Datei anzeigen

@ -33,6 +33,7 @@ fun Application.configureRoutes() {
configureUserPerms()
configureStats()
configurePage()
configureSchematic()
}
}
}

Datei anzeigen

@ -0,0 +1,216 @@
package de.steamwar.routes
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.NodeData
import de.steamwar.sql.NodeDownload
import de.steamwar.sql.NodeMember
import de.steamwar.sql.SWException
import de.steamwar.sql.SchematicNode
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import java.security.MessageDigest
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalUnit
import java.util.*
@Serializable
data class ResponseSchematic(val name: String, val id: Int, val type: String?, val owner: Int, val item: String, val lastUpdate: Long, val rank: Int, val replaceColor: Boolean, val allowReplay: Boolean) {
constructor(node: SchematicNode) : this(node.name, node.id, node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
}
@Serializable
data class ResponseSchematicLong(val members: List<ResponseUser>, val path: String, val schem: ResponseSchematic) {
constructor(node: SchematicNode, path: String): this(NodeMember.getNodeMembers(node.id).map { ResponseUser.get(it.member) }, path, ResponseSchematic(node))
}
@Serializable
data class ResponseSchematicList(val breadcrumbs: List<ResponseBreadcrumb>, val schematics: List<ResponseSchematic>, val players: Map<Int, ResponseUser>) {
constructor(schematics: List<ResponseSchematic>, breadcrumbs: List<ResponseBreadcrumb>) : this(breadcrumbs, schematics, schematics.map { it.owner }.distinct().map { ResponseUser.get(it) }.associateBy { it.id })
}
@Serializable
data class ResponseBreadcrumb(val name: String, val id: Int)
fun generateCode(): String {
val md = MessageDigest.getInstance("SHA-256")
val random = ByteArray(64).map { (0..255).random().toByte() }.toByteArray()
val code = md.digest(random)
return code.joinToString("") { "%02x".format(it) }
}
@Serializable
data class SchematicCode(val id: Int, val code: String, val expires: Long)
@Serializable
data class UploadSchematic(val name: String, val content: String)
fun Route.configureSchematic() {
get("/download/{code}") {
val code = call.parameters["code"] ?: run {
call.respond(HttpStatusCode.BadRequest)
return@get
}
val dl = NodeDownload.get(code) ?: run {
call.respond(HttpStatusCode.NotFound)
return@get
}
dl.delete()
if(dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
call.respond(HttpStatusCode.Gone)
return@get
}
val node = SchematicNode.getSchematicNode(dl.nodeId) ?: run {
call.respond(HttpStatusCode.NotFound)
return@get
}
val user = call.principal<SWAuthPrincipal>()?.user
if(user != null && !node.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden)
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
return@get
}
val data = NodeData.get(node) ?: run {
call.respond(HttpStatusCode.InternalServerError)
return@get
}
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}.${if (data.nodeFormat) "schem" else "schematic"}\"")
call.respondBytes(data.schemData().readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
}
route("/schem") {
install(SWPermissionCheck)
get {
val user = call.principal<SWAuthPrincipal>()!!.user
call.respond(ResponseSchematicList(SchematicNode.list(user, null).sortedWith { o1, o2 ->
if (o1.isDir || o2.isDir) {
o2.isDir.compareTo(o1.isDir)
} else {
o1.name.compareTo(o2.name)
}
}.map { ResponseSchematic(it) }, listOf()))
}
post {
val file = call.receive<UploadSchematic>()
val schemName = file.name.substringBeforeLast(".")
val schemType = file.name.substringAfterLast(".")
if (schemType != "schem" && schemType != "schematic") {
call.respond(HttpStatusCode.BadRequest)
return@post
}
val user = call.principal<SWAuthPrincipal>()!!.user
val content = Base64.getDecoder().decode(file.content)
var node = SchematicNode.getSchematicNode(user.id, schemName, 0)
if (node == null) {
node = SchematicNode.createSchematic(user.id, schemName, 0)
}
val data = NodeData(node.id, false)
data.saveFromStream(content.inputStream(), schemType == "schem")
call.respond(ResponseSchematic(node))
}
route("/{id}") {
get {
val user = call.principal<SWAuthPrincipal>()!!.user
val parentId = call.parameters["id"]?.toIntOrNull()
if(parentId == null) {
call.respond(HttpStatusCode.BadRequest)
return@get
}
val parent = SchematicNode.getSchematicNode(parentId)
if(parent == null) {
call.respond(HttpStatusCode.NotFound)
return@get
}
if(!parent.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden)
return@get
}
call.respond(ResponseSchematicLong(parent, parent.generateBreadcrumbs(user)))
}
get("/list") {
val user = call.principal<SWAuthPrincipal>()!!.user
val parentId = call.parameters["id"]?.toIntOrNull()
if(parentId == null) {
call.respond(HttpStatusCode.BadRequest)
return@get
}
val parent = SchematicNode.getSchematicNode(parentId)
if(parent == null) {
call.respond(HttpStatusCode.NotFound)
return@get
}
if(!parent.isDir) {
call.respond(HttpStatusCode.BadRequest)
return@get
}
if(!parent.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden)
return@get
}
call.respond(ResponseSchematicList(SchematicNode.list(user, parent.id).sortedWith { o1, o2 ->
if (o1.isDir || o2.isDir) {
o2.isDir.compareTo(o1.isDir)
} else {
o1.name.compareTo(o2.name)
}
}.map { ResponseSchematic(it) }, parent.generateBreadcrumbsMap(user).map { ResponseBreadcrumb(it.key, it.value) }))
}
get("/download") {
val user = call.principal<SWAuthPrincipal>()!!.user
val schemId = call.parameters["id"]?.toIntOrNull() ?: run {
call.respond(HttpStatusCode.BadRequest)
return@get
}
val schem = SchematicNode.getSchematicNode(schemId) ?: run {
call.respond(HttpStatusCode.NotFound)
return@get
}
if(!schem.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden)
return@get
}
val code = generateCode()
val dl = NodeDownload.addCode(schem, code)
val response = SchematicCode(schemId, code, dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).epochSecond);
call.respond(response)
}
}
}
}

Datei anzeigen

@ -25,14 +25,14 @@ private fun readVarInt(`in`: DataInputStream): Int {
}
private fun writeVarInt(out: DataOutputStream, paramInt: Int) {
var paramInt = paramInt
var paramInts = paramInt
while (true) {
if (paramInt and -0x80 == 0) {
out.writeByte(paramInt)
if (paramInts and -0x80 == 0) {
out.writeByte(paramInts)
return
}
out.writeByte(paramInt and 0x7F or 0x80)
paramInt = paramInt ushr 7
out.writeByte(paramInts and 0x7F or 0x80)
paramInts = paramInts ushr 7
}
}
@ -61,7 +61,7 @@ fun fetchData(address: InetSocketAddress, timeout: Int = 7000): StatusResponse {
dataOutputStream.writeByte(0x01) //size is only 1
dataOutputStream.writeByte(0x00) //packet id for ping
val dataInputStream = DataInputStream(inputStream)
val size = readVarInt(dataInputStream) //size of packet
readVarInt(dataInputStream) //size of packet
var id = readVarInt(dataInputStream) //packet id
if (id == -1) {
throw IOException("Premature end of stream.")