Updates
Dieser Commit ist enthalten in:
Ursprung
dfc3fd0b3b
Commit
c9013a69c6
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,3 +35,4 @@ out/
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
/config.json
|
||||
/logs/
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit fa049455956e40efd126082c1eef470698263abb
|
||||
Subproject commit 61985fb80673b318a9a9b6bfa345b670ac0e1da9
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -33,6 +33,7 @@ fun Application.configureRoutes() {
|
||||
configureUserPerms()
|
||||
configureStats()
|
||||
configurePage()
|
||||
configureSchematic()
|
||||
}
|
||||
}
|
||||
}
|
216
src/main/kotlin/de/steamwar/routes/Schematic.kt
Normale Datei
216
src/main/kotlin/de/steamwar/routes/Schematic.kt
Normale Datei
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.")
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren