Open to Public
Dieser Commit ist enthalten in:
Ursprung
eaff459715
Commit
a81a7e758f
@ -24,18 +24,22 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||
implementation("io.ktor:ktor-server-core-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-server-cors-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-server-jetty-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-server-host-common-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-server-call-logging-jvm:2.3.0")
|
||||
implementation("io.ktor:ktor-server-request-validation:2.3.0")
|
||||
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-cors-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-jetty-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-call-logging-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-request-validation:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-auth:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-auth-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-auth-ldap-jvm:$ktor_version")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
||||
implementation("com.mysql:mysql-connector-j:8.0.31")
|
||||
implementation("com.mysql:mysql-connector-j:8.1.0")
|
||||
implementation(project(":CommonCore"))
|
||||
implementation("org.bspfsystems:yamlconfiguration:1.3.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.4.1")
|
||||
testImplementation("io.ktor:ktor-server-tests-jvm:2.3.0")
|
||||
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-rate-limit:$ktor_version")
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
ktor_version=2.2.1
|
||||
ktor_version=2.3.4
|
||||
kotlin_version=1.7.22
|
||||
logback_version=1.2.11
|
||||
kotlin.code.style=official
|
||||
|
@ -19,42 +19,66 @@
|
||||
|
||||
package de.steamwar.plugins
|
||||
|
||||
import de.steamwar.ResponseError
|
||||
import de.steamwar.sql.SteamwarUser
|
||||
import de.steamwar.sql.Token
|
||||
import de.steamwar.sql.UserPerm
|
||||
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 java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.util.Properties
|
||||
|
||||
val validCodes by lazy {
|
||||
val file = File(System.getProperty("user.home"), "api-codes.txt")
|
||||
if (!file.exists()) {
|
||||
file.createNewFile()
|
||||
|
||||
data class SWAuthPrincipal(val token: Token, val user: SteamwarUser) : Principal
|
||||
|
||||
class SWAuthConfig {
|
||||
public var permission: UserPerm = UserPerm.MODERATION
|
||||
public var allowedMethods = mutableListOf<HttpMethod>()
|
||||
public var userCheck: SWAuthPrincipal.(ApplicationRequest) -> Boolean = { true }
|
||||
public var mustAuth: Boolean = false
|
||||
|
||||
fun allowMethod(method: HttpMethod) {
|
||||
allowedMethods.add(method)
|
||||
}
|
||||
|
||||
fun allowMethods(methods: List<HttpMethod>) {
|
||||
allowedMethods.addAll(methods)
|
||||
}
|
||||
|
||||
fun userCheck(check: SWAuthPrincipal.(ApplicationRequest) -> Boolean) {
|
||||
userCheck = check
|
||||
}
|
||||
Properties().apply {
|
||||
load(file.inputStream())
|
||||
}.map { it.key.toString() to it.value.toString() }.toMap()
|
||||
}
|
||||
|
||||
fun isValidCode(code: String): Pair<Boolean, String> {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
val hash = digest.digest(code.toByteArray())
|
||||
val hashed = hash.joinToString("") { "%02x".format(it) }
|
||||
return validCodes.contains(hashed) to hashed
|
||||
}
|
||||
val SWPermissionCheck = createRouteScopedPlugin("SWAuth", ::SWAuthConfig) {
|
||||
pluginConfig.apply {
|
||||
on(AuthenticationChecked) { call ->
|
||||
if (call.request.httpMethod in allowedMethods) {
|
||||
if(mustAuth) {
|
||||
val token = call.principal<SWAuthPrincipal>()
|
||||
|
||||
val SWAuth = createApplicationPlugin("SWAuth") {
|
||||
onCall { call ->
|
||||
if (call.request.httpMethod == HttpMethod.Options) {
|
||||
return@onCall
|
||||
}
|
||||
val auth = call.request.headers["X-SW-Auth"] ?: call.request.queryParameters["auth"]
|
||||
if (auth == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized, ResponseError("Missing auth header"))
|
||||
} else if (!isValidCode(auth).first) {
|
||||
call.respond(HttpStatusCode.Unauthorized, ResponseError("Invalid auth code"))
|
||||
if (token == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized, "Invalid token")
|
||||
}
|
||||
}
|
||||
|
||||
return@on
|
||||
}
|
||||
|
||||
val token = call.principal<SWAuthPrincipal>()
|
||||
|
||||
if (token == null) {
|
||||
call.respond(HttpStatusCode.Unauthorized, "Invalid token")
|
||||
return@on
|
||||
}
|
||||
|
||||
if (!token.user.hasPerm(permission)) {
|
||||
call.respond(HttpStatusCode.Forbidden, "Insufficient permissions")
|
||||
}
|
||||
|
||||
if (!token.userCheck(call.request)) {
|
||||
call.respond(HttpStatusCode.Forbidden, "Insufficient permissions")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,17 +19,28 @@
|
||||
|
||||
package de.steamwar.plugins
|
||||
|
||||
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.auth.*
|
||||
import io.ktor.server.plugins.callloging.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.plugins.cors.routing.*
|
||||
import io.ktor.server.plugins.ratelimit.*
|
||||
import io.ktor.server.request.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.slf4j.event.*
|
||||
import java.security.MessageDigest
|
||||
import java.util.Base64
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
data class Session(val name: String)
|
||||
fun hashToken(token: String): String {
|
||||
val md = MessageDigest.getInstance("SHA-512")
|
||||
return Base64.getEncoder().encodeToString(md.digest(token.toByteArray()))
|
||||
}
|
||||
|
||||
fun Application.configurePlugins() {
|
||||
install(CORS) {
|
||||
@ -44,19 +55,35 @@ fun Application.configurePlugins() {
|
||||
anyHost()
|
||||
allowXHttpMethodOverride()
|
||||
}
|
||||
install(SWAuth)
|
||||
install(RateLimit) {
|
||||
global {
|
||||
rateLimiter(limit = 60, refillPeriod = 60.seconds)
|
||||
}
|
||||
}
|
||||
authentication {
|
||||
bearer("sw-auth") {
|
||||
realm = "SteamWar API"
|
||||
authenticate { call ->
|
||||
val token = Token.get(hashToken(call.token))
|
||||
if (token == null) {
|
||||
null
|
||||
} else {
|
||||
SWAuthPrincipal(token, token.owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
install(ContentNegotiation) {
|
||||
json(Json)
|
||||
}
|
||||
install(CallLogging) {
|
||||
level = Level.INFO
|
||||
format {
|
||||
val auth = it.request.headers["X-SW-Auth"]
|
||||
if (auth != null) {
|
||||
val verfied = isValidCode(auth)
|
||||
return@format "Auth: ${verfied.second}, Valid: ${verfied.first}, ${it.request.httpMethod.value} ${it.request.uri}"
|
||||
val verified = it.principal<SWAuthPrincipal>()
|
||||
if (verified != null) {
|
||||
"User: ${verified.token.owner.userName}, Token: ${verified.token.name}, ${it.request.httpMethod.value} ${it.request.uri}"
|
||||
} else {
|
||||
return@format "No Auth Header found: ${it.request.httpMethod.value} ${it.request.uri}"
|
||||
"Unauthenticated Request: ${it.request.httpMethod.value} ${it.request.uri}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ package de.steamwar.plugins
|
||||
import de.steamwar.sql.SteamwarUser
|
||||
import io.ktor.server.request.*
|
||||
|
||||
fun ApplicationRequest.getUser(): SteamwarUser? {
|
||||
fun ApplicationRequest.getUser(key: String = "id"): SteamwarUser? {
|
||||
SteamwarUser.clear()
|
||||
return SteamwarUser.get(call.parameters["id"]?.toIntOrNull() ?: return null)
|
||||
return SteamwarUser.get(call.parameters[key]?.toIntOrNull() ?: return null)
|
||||
}
|
@ -40,7 +40,7 @@ data class ResponseUser(val id: Int, val name: String, val uuid: String) {
|
||||
constructor(user: SteamwarUser) : this(user.id, user.userName, user.uuid.toString())
|
||||
}
|
||||
|
||||
fun Routing.configureDataRoutes() {
|
||||
fun Route.configureDataRoutes() {
|
||||
route("/data") {
|
||||
get("/schematicTypes") {
|
||||
val types = mutableListOf<SchematicType>()
|
||||
@ -49,7 +49,7 @@ fun Routing.configureDataRoutes() {
|
||||
}
|
||||
get("/gamemodes") {
|
||||
call.respond(
|
||||
File("/configs/GameModes/").listFiles()
|
||||
File("/configs/GameModes/").listFiles()!!
|
||||
.filter { it.name.endsWith(".yml") && !it.name.endsWith(".kits.yml") }
|
||||
.map { it.nameWithoutExtension })
|
||||
}
|
||||
|
@ -21,9 +21,11 @@ package de.steamwar.routes
|
||||
|
||||
import de.steamwar.ResponseError
|
||||
import de.steamwar.data.Groups
|
||||
import de.steamwar.plugins.SWPermissionCheck
|
||||
import de.steamwar.sql.EventFight
|
||||
import de.steamwar.sql.SteamwarUser
|
||||
import de.steamwar.sql.Team
|
||||
import de.steamwar.sql.UserPerm
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.request.*
|
||||
@ -52,8 +54,12 @@ data class UpdateEventFight(val blueTeam: Int? = null, val redTeam: Int? = null
|
||||
@Serializable
|
||||
data class CreateEventFight(val event: Int, val spielmodus: String, val map: String, val blueTeam: Int, val redTeam: Int, val start: Long, val kampfleiter: Int? = null, val group: String? = null)
|
||||
|
||||
fun Routing.configureEventFightRoutes() {
|
||||
fun Route.configureEventFightRoutes() {
|
||||
route("/fights") {
|
||||
install(SWPermissionCheck) {
|
||||
allowMethod(HttpMethod.Get)
|
||||
permission = UserPerm.MODERATION
|
||||
}
|
||||
post {
|
||||
val fight = call.receiveNullable<CreateEventFight>()
|
||||
if (fight == null) {
|
||||
|
@ -21,11 +21,8 @@ package de.steamwar.routes
|
||||
|
||||
import de.steamwar.ResponseError
|
||||
import de.steamwar.data.Groups
|
||||
import de.steamwar.sql.Event
|
||||
import de.steamwar.sql.EventFight
|
||||
import de.steamwar.sql.SchematicType
|
||||
import de.steamwar.sql.Team
|
||||
import de.steamwar.sql.TeamTeilnahme
|
||||
import de.steamwar.plugins.SWPermissionCheck
|
||||
import de.steamwar.sql.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.request.*
|
||||
@ -88,8 +85,12 @@ data class UpdateEvent(
|
||||
val spectateSystem: Boolean?
|
||||
)
|
||||
|
||||
fun Routing.configureEventsRoute() {
|
||||
fun Route.configureEventsRoute() {
|
||||
route("/events") {
|
||||
install(SWPermissionCheck) {
|
||||
allowMethod(HttpMethod.Get)
|
||||
permission = UserPerm.MODERATION
|
||||
}
|
||||
get {
|
||||
call.respond(Event.getAllShort().map { ShortEvent(it) })
|
||||
}
|
||||
|
@ -20,7 +20,9 @@
|
||||
package de.steamwar.routes
|
||||
|
||||
import de.steamwar.ResponseError
|
||||
import de.steamwar.plugins.SWPermissionCheck
|
||||
import de.steamwar.sql.Mod
|
||||
import de.steamwar.sql.UserPerm
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.request.*
|
||||
@ -36,8 +38,12 @@ data class ResponseMod(val platform: Int, val modName: String, val modType: Int)
|
||||
@Serializable
|
||||
data class UpdateMod(val modType: String)
|
||||
|
||||
fun Routing.configureModRoutes() {
|
||||
fun Route.configureModRoutes() {
|
||||
route("/mods") {
|
||||
install(SWPermissionCheck) {
|
||||
allowMethod(HttpMethod.Get)
|
||||
permission = UserPerm.MODERATION
|
||||
}
|
||||
get("/unchecked") {
|
||||
call.respond(Mod.getAllUnklassified().map { ResponseMod(it) })
|
||||
}
|
||||
|
@ -20,14 +20,17 @@
|
||||
package de.steamwar.routes
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureRoutes() {
|
||||
routing {
|
||||
configureEventsRoute()
|
||||
configureDataRoutes()
|
||||
configureEventFightRoutes()
|
||||
configureModRoutes()
|
||||
configureUserPerms()
|
||||
authenticate("sw-auth", optional = true) {
|
||||
configureEventsRoute()
|
||||
configureDataRoutes()
|
||||
configureEventFightRoutes()
|
||||
configureModRoutes()
|
||||
configureUserPerms()
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ import io.ktor.server.application.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Routing.configureTeamRoutes() {
|
||||
fun Route.configureTeamRoutes() {
|
||||
route("/team") {
|
||||
get {
|
||||
call.respond(Team.getAll().map { ResponseTeam(it) })
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
package de.steamwar.routes
|
||||
|
||||
import de.steamwar.plugins.SWPermissionCheck
|
||||
import de.steamwar.plugins.getUser
|
||||
import de.steamwar.sql.SteamwarUser
|
||||
import de.steamwar.sql.UserPerm
|
||||
@ -38,8 +39,12 @@ data class RespondUserPerms(val prefixes: Map<String, RespondPrefix>, val perms:
|
||||
@Serializable
|
||||
data class RespondUserPermsPrefix(val prefix: RespondPrefix, val perms: List<String>)
|
||||
|
||||
fun Routing.configureUserPerms() {
|
||||
fun Route.configureUserPerms() {
|
||||
route("/perms") {
|
||||
install(SWPermissionCheck) {
|
||||
allowMethod(HttpMethod.Get)
|
||||
permission = UserPerm.MODERATION
|
||||
}
|
||||
get {
|
||||
val perms = mutableListOf<String>()
|
||||
val prefixes = mutableMapOf<String, RespondPrefix>()
|
||||
@ -55,6 +60,11 @@ fun Routing.configureUserPerms() {
|
||||
call.respond(RespondUserPerms(prefixes, perms))
|
||||
}
|
||||
route("/user/{id}") {
|
||||
install(SWPermissionCheck) {
|
||||
allowMethod(HttpMethod.Get)
|
||||
permission = UserPerm.MODERATION
|
||||
mustAuth = true
|
||||
}
|
||||
get {
|
||||
val user = call.request.getUser()
|
||||
if (user == null) {
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren