Merge pull request #7 from bluefireoly/badipdetection
Bad IP detection functionality
This commit is contained in:
@@ -57,6 +57,9 @@ dependencies {
|
||||
compileOnly("org.spigotmc", "spigot", "1.16.3-R0.1-SNAPSHOT")
|
||||
testCompileOnly("org.spigotmc", "spigot", "1.16.3-R0.1-SNAPSHOT")
|
||||
|
||||
// KHTTP
|
||||
implementation("khttp", "khttp", "1.0.0")
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
package net.axay.kspigot.config
|
||||
|
||||
import net.axay.kspigot.kotlinextensions.createIfNotExists
|
||||
import net.axay.kspigot.languageextensions.createIfNotExists
|
||||
import net.axay.kspigot.main.ValueHolder.getGson
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
@@ -83,7 +83,7 @@ class ConfigDelegate<T : Any>(
|
||||
internal object GsonConfigManager {
|
||||
|
||||
fun <T : Any> loadConfig(file: File, configClass: KClass<T>): T =
|
||||
FileReader(file).use { reader -> return getGson(false).fromJson(reader, configClass.java) }
|
||||
FileReader(file).use { reader -> return getGson().fromJson(reader, configClass.java) }
|
||||
|
||||
fun <T : Any> saveConfig(file: File, config: T, pretty: Boolean = true) {
|
||||
file.createIfNotExists()
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
package net.axay.kspigot.inventory
|
||||
|
||||
import net.axay.kspigot.kotlinextensions.MinMaxPair
|
||||
import net.axay.kspigot.languageextensions.MinMaxPair
|
||||
|
||||
// INVENTORY
|
||||
|
||||
|
118
src/main/kotlin/net/axay/kspigot/ipaddress/BadIPDetection.kt
Normal file
118
src/main/kotlin/net/axay/kspigot/ipaddress/BadIPDetection.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package net.axay.kspigot.ipaddress
|
||||
|
||||
import net.axay.kspigot.ipaddress.badipdetectionservices.GetIPIntel
|
||||
import net.axay.kspigot.ipaddress.badipdetectionservices.IPHub
|
||||
import org.bukkit.entity.Player
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Checks if the IP address of the player is not a
|
||||
* normal residential address.
|
||||
*
|
||||
* This function returns true if just one of all
|
||||
* available detection services detects a bad IP address.
|
||||
*
|
||||
* @param detector The compound of detection services.
|
||||
*/
|
||||
fun Player.hasBadIP(detector: BadIPDetector = BadIPDetector.DEFAULT) =
|
||||
checkIP(detector).filterValues { it.isBad }.isNotEmpty()
|
||||
|
||||
/**
|
||||
* Checks if the IP address of the player is not a
|
||||
* normal residential address.
|
||||
*
|
||||
* This function returns the result of each
|
||||
* detection service given by the [detector].
|
||||
*
|
||||
* @param detector The compound of detection services.
|
||||
*/
|
||||
fun Player.checkIP(
|
||||
detector: BadIPDetector = BadIPDetector.DEFAULT,
|
||||
breakOnHit: Boolean = true
|
||||
): Map<BadIPDetectionService, BadIPDetectionResult> {
|
||||
val ip = address?.hostString ?: return emptyMap()
|
||||
return detector.checkIP(ip, breakOnHit)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param services A list of [BadIPDetectionService]s.
|
||||
* The order matters!
|
||||
*/
|
||||
class BadIPDetector(
|
||||
val services: List<BadIPDetectionService>
|
||||
) {
|
||||
|
||||
constructor(vararg services: BadIPDetectionService) : this(services.toList())
|
||||
|
||||
companion object {
|
||||
val DEFAULT = BadIPDetector(
|
||||
listOf(
|
||||
GetIPIntel(),
|
||||
IPHub()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun checkIP(ip: String, breakOnHit: Boolean = true) =
|
||||
HashMap<BadIPDetectionService, BadIPDetectionResult>().apply {
|
||||
for (it in services) {
|
||||
|
||||
val curResult = it.isBad(ip)
|
||||
this[it] = curResult
|
||||
|
||||
if (curResult.isBad && breakOnHit) break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class BadIPDetectionResult(
|
||||
val isBad: Boolean,
|
||||
val typeName: String
|
||||
) {
|
||||
|
||||
GENERAL_BAD(true, "bad ip"),
|
||||
|
||||
VPN(true, "vpn"),
|
||||
PROXY(true, "proxy"),
|
||||
TOR(true, "tor network"),
|
||||
HOSTING(true, "hosting"),
|
||||
|
||||
GOOD(false, "valid ip"),
|
||||
ERROR(false, "error"),
|
||||
LIMIT(false, "limit");
|
||||
|
||||
}
|
||||
|
||||
abstract class BadIPDetectionService(
|
||||
val name: String
|
||||
) {
|
||||
|
||||
protected abstract fun requestString(ip: String): String
|
||||
|
||||
protected abstract fun interpreteResult(result: JSONObject): BadIPDetectionResult
|
||||
|
||||
fun isBad(ip: String): BadIPDetectionResult {
|
||||
val response = khttp.get(requestString(ip))
|
||||
if (response.statusCode == 429)
|
||||
return BadIPDetectionResult.LIMIT
|
||||
else {
|
||||
|
||||
val result = try {
|
||||
response.jsonObject
|
||||
} catch (exc: JSONException) {
|
||||
null
|
||||
} ?: return BadIPDetectionResult.ERROR
|
||||
|
||||
return try {
|
||||
interpreteResult(result)
|
||||
} catch (exc: Exception) {
|
||||
return BadIPDetectionResult.ERROR
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -3,9 +3,10 @@
|
||||
package net.axay.kspigot.ipaddress
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import net.axay.kspigot.languageextensions.fromUrlJson
|
||||
import net.axay.kspigot.languageextensions.getStringOrNull
|
||||
import net.axay.kspigot.main.ValueHolder
|
||||
import org.bukkit.entity.Player
|
||||
import java.net.URL
|
||||
|
||||
private const val IP_API = "http://ip-api.com/json/"
|
||||
private const val IP_API_FIELDS =
|
||||
@@ -27,9 +28,8 @@ fun Player.ipAddressData(language: IPAddressDataLanguage = IPAddressDataLanguage
|
||||
|
||||
val hostString = address?.hostString ?: return null
|
||||
|
||||
val jsonObject = ValueHolder.getGson(false).fromJson(
|
||||
URL("$IP_API${hostString}?fields=${IP_API_FIELDS}?lang=${language.code}").readText(),
|
||||
JsonObject::class.java
|
||||
val jsonObject = ValueHolder.getGson().fromUrlJson(
|
||||
"$IP_API${hostString}?fields=${IP_API_FIELDS}?lang=${language.code}"
|
||||
) ?: return null
|
||||
|
||||
if (jsonObject["status"].toString() == "fail") return null
|
||||
@@ -55,32 +55,26 @@ enum class IPAddressDataLanguage(val code: String) {
|
||||
|
||||
class IPAddressData(private val json: JsonObject) {
|
||||
|
||||
val ip get() = json.getString("query")
|
||||
val ip get() = json.getStringOrNull("query")
|
||||
|
||||
// region
|
||||
val continent get() = json.getString("continent")
|
||||
val continentCode get() = json.getString("continentCode")
|
||||
val country get() = json.getString("country")
|
||||
val countryCode get() = json.getString("countryCode")
|
||||
val region get() = json.getString("regionName")
|
||||
val regionCode get() = json.getString("region")
|
||||
val city get() = json.getString("city")
|
||||
val district get() = json.getString("district")
|
||||
val postalCode get() = json.getString("zip")
|
||||
val timezone get() = json.getString("timezone")
|
||||
val continent get() = json.getStringOrNull("continent")
|
||||
val continentCode get() = json.getStringOrNull("continentCode")
|
||||
val country get() = json.getStringOrNull("country")
|
||||
val countryCode get() = json.getStringOrNull("countryCode")
|
||||
val region get() = json.getStringOrNull("regionName")
|
||||
val regionCode get() = json.getStringOrNull("region")
|
||||
val city get() = json.getStringOrNull("city")
|
||||
val district get() = json.getStringOrNull("district")
|
||||
val postalCode get() = json.getStringOrNull("zip")
|
||||
val timezone get() = json.getStringOrNull("timezone")
|
||||
|
||||
// position
|
||||
val latitude get() = json.getString("lat")
|
||||
val longitude get() = json.getString("lon")
|
||||
val latitude get() = json.getStringOrNull("lat")
|
||||
val longitude get() = json.getStringOrNull("lon")
|
||||
|
||||
// information
|
||||
val internetServiceProvider get() = json.getString("isp")
|
||||
val organisation get() = json.getString("org")
|
||||
val internetServiceProvider get() = json.getStringOrNull("isp")
|
||||
val organisation get() = json.getStringOrNull("org")
|
||||
|
||||
}
|
||||
|
||||
private fun JsonObject.getString(key: String) = try {
|
||||
this[key].toString()
|
||||
} catch (exc: Exception) {
|
||||
null
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.axay.kspigot.ipaddress.badipdetectionservices
|
||||
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionResult
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionService
|
||||
import net.axay.kspigot.languageextensions.getStringOrNull
|
||||
import org.json.JSONObject
|
||||
|
||||
class GetIPIntel(
|
||||
private val intensity: Float = 0.99f,
|
||||
private val contactEmail: String = "foo@bar.com"
|
||||
) : BadIPDetectionService("getipintel.net") {
|
||||
|
||||
override fun requestString(ip: String) = "http://check.getipintel.net/check.php?ip=$ip&contact=$contactEmail"
|
||||
|
||||
override fun interpreteResult(result: JSONObject): BadIPDetectionResult {
|
||||
val probability = result.getStringOrNull("result")?.toFloatOrNull()
|
||||
?: return BadIPDetectionResult.ERROR
|
||||
return if (probability >= intensity) BadIPDetectionResult.GENERAL_BAD else BadIPDetectionResult.GOOD
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.axay.kspigot.ipaddress.badipdetectionservices
|
||||
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionResult
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionService
|
||||
import net.axay.kspigot.languageextensions.getStringOrNull
|
||||
import org.json.JSONObject
|
||||
|
||||
class IPHub(
|
||||
private val ifStrict: Boolean = false
|
||||
) : BadIPDetectionService("iphub.info") {
|
||||
|
||||
override fun requestString(ip: String) = "http://v2.api.iphub.info/ip/$ip"
|
||||
|
||||
override fun interpreteResult(result: JSONObject): BadIPDetectionResult {
|
||||
val ifBlock = result.getStringOrNull("block")?.toInt() ?: return BadIPDetectionResult.ERROR
|
||||
return if (ifBlock == 1 || (ifStrict && ifBlock == 2)) BadIPDetectionResult.GENERAL_BAD else BadIPDetectionResult.GOOD
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package net.axay.kspigot.ipaddress.badipdetectionservices
|
||||
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionResult
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionService
|
||||
import net.axay.kspigot.languageextensions.getStringOrNull
|
||||
import org.json.JSONObject
|
||||
|
||||
class IPInfo(
|
||||
private val token: String
|
||||
) : BadIPDetectionService("ipinfo.io") {
|
||||
|
||||
override fun requestString(ip: String) = "https://ipinfo.io/$ip/privacy?token=$token"
|
||||
|
||||
override fun interpreteResult(result: JSONObject): BadIPDetectionResult {
|
||||
return when {
|
||||
result.getStringOrNull("vpn").toBoolean() -> BadIPDetectionResult.VPN
|
||||
result.getStringOrNull("proxy").toBoolean() -> BadIPDetectionResult.PROXY
|
||||
result.getStringOrNull("tor").toBoolean() -> BadIPDetectionResult.TOR
|
||||
result.getStringOrNull("hosting").toBoolean() -> BadIPDetectionResult.HOSTING
|
||||
else -> BadIPDetectionResult.GOOD
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package net.axay.kspigot.ipaddress.badipdetectionservices
|
||||
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionResult
|
||||
import net.axay.kspigot.ipaddress.BadIPDetectionService
|
||||
import net.axay.kspigot.languageextensions.getStringOrNull
|
||||
import org.json.JSONObject
|
||||
|
||||
class VPNBlocker : BadIPDetectionService("vpnblocker.net") {
|
||||
|
||||
override fun requestString(ip: String) = "http://api.vpnblocker.net/v2/json/$ip"
|
||||
|
||||
override fun interpreteResult(result: JSONObject): BadIPDetectionResult {
|
||||
val isBad = result.getStringOrNull("host-ip")
|
||||
return when {
|
||||
isBad != null -> if (isBad.toBoolean()) BadIPDetectionResult.GENERAL_BAD else BadIPDetectionResult.GOOD
|
||||
else -> {
|
||||
val remaining = result.getStringOrNull("remaining_requests")?.toIntOrNull()
|
||||
?: return BadIPDetectionResult.ERROR
|
||||
if (remaining <= 0) BadIPDetectionResult.LIMIT else BadIPDetectionResult.ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
package net.axay.kspigot.kotlinextensions
|
||||
|
||||
import java.io.File
|
||||
|
||||
internal inline fun <T, R> Lazy<T>.ifInitialized(block: (T) -> R) = if (isInitialized()) block(value) else null
|
||||
|
||||
internal val <T> Lazy<T>.valueIfInitialized get() = ifInitialized { value }
|
||||
|
||||
internal fun Lazy<AutoCloseable>.closeIfInitialized() = ifInitialized { value.close() }
|
||||
|
||||
internal class MinMaxPair<T : Comparable<T>>(a: T, b: T) {
|
||||
val min: T;
|
||||
val max: T
|
||||
|
||||
init {
|
||||
if (a >= b) {
|
||||
min = b; max = a
|
||||
} else {
|
||||
min = a; max = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T> T.applyIfNotNull(block: (T.() -> Unit)?): T {
|
||||
if (block != null)
|
||||
apply(block)
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun File.createIfNotExists(): Boolean {
|
||||
return if (!exists()) {
|
||||
if (!parentFile.exists())
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
} else true
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package net.axay.kspigot.languageextensions
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import java.net.URL
|
||||
|
||||
internal fun JsonObject.getStringOrNull(key: String): String? {
|
||||
return try {
|
||||
this[key].toString()
|
||||
} catch (exc: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Gson.fromUrlJson(url: String): JsonObject? {
|
||||
return fromJson(
|
||||
URL(url).readText(),
|
||||
JsonObject::class.java
|
||||
) ?: return null
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package net.axay.kspigot.languageextensions
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
internal fun JSONObject.getStringOrNull(key: String): String? {
|
||||
return try {
|
||||
this[key].toString()
|
||||
} catch (exc: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package net.axay.kspigot.languageextensions.kotlinextensions
|
||||
|
||||
import java.io.File
|
||||
|
||||
internal fun File.createIfNotExists(): Boolean {
|
||||
return if (!exists()) {
|
||||
if (!parentFile.exists())
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
} else true
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package net.axay.kspigot.languageextensions.kotlinextensions
|
||||
|
||||
internal fun <T> T.applyIfNotNull(block: (T.() -> Unit)?): T {
|
||||
if (block != null)
|
||||
apply(block)
|
||||
return this
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package net.axay.kspigot.languageextensions.kotlinextensions
|
||||
|
||||
internal inline fun <T, R> Lazy<T>.ifInitialized(block: (T) -> R) = if (isInitialized()) block(value) else null
|
||||
|
||||
internal val <T> Lazy<T>.valueIfInitialized get() = ifInitialized { value }
|
||||
|
||||
internal fun Lazy<AutoCloseable>.closeIfInitialized() = ifInitialized { value.close() }
|
@@ -0,0 +1,18 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.axay.kspigot.languageextensions.kotlinextensions
|
||||
|
||||
internal class MinMaxPair<T : Comparable<T>>(a: T, b: T) {
|
||||
|
||||
val min: T;
|
||||
val max: T
|
||||
|
||||
init {
|
||||
if (a >= b) {
|
||||
min = b; max = a
|
||||
} else {
|
||||
min = a; max = b
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package net.axay.kspigot.main
|
||||
|
||||
import net.axay.kspigot.inventory.InventoryGUIHolder
|
||||
import net.axay.kspigot.kotlinextensions.closeIfInitialized
|
||||
import net.axay.kspigot.languageextensions.closeIfInitialized
|
||||
import net.axay.kspigot.runnables.KRunnableHolder
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
||||
|
@@ -12,6 +12,6 @@ object ValueHolder {
|
||||
private val gson: Gson by lazy { gsonBuilder.create() }
|
||||
private val gsonPretty: Gson by lazy { gsonBuilder.setPrettyPrinting().create() }
|
||||
|
||||
fun getGson(pretty: Boolean) = if (pretty) gsonPretty else gson
|
||||
fun getGson(pretty: Boolean = false) = if (pretty) gsonPretty else gson
|
||||
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package net.axay.kspigot.particles
|
||||
|
||||
import net.axay.kspigot.extensions.bukkit.worldOrException
|
||||
import net.axay.kspigot.kotlinextensions.applyIfNotNull
|
||||
import net.axay.kspigot.languageextensions.applyIfNotNull
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.Particle
|
||||
import org.bukkit.entity.Player
|
||||
|
@@ -23,4 +23,4 @@ fun SpigotSerializable<*>.serialize(pretty: Boolean = true): String = ValueHolde
|
||||
*/
|
||||
@Suppress("unused")
|
||||
inline fun <reified T> SpigotSerialzableCompanion<T>.deserialize(json: String): T =
|
||||
ValueHolder.getGson(false).fromJson(json, T::class.java)
|
||||
ValueHolder.getGson().fromJson(json, T::class.java)
|
@@ -1,7 +1,7 @@
|
||||
package net.axay.kspigot.sound
|
||||
|
||||
import net.axay.kspigot.extensions.bukkit.worldOrException
|
||||
import net.axay.kspigot.kotlinextensions.applyIfNotNull
|
||||
import net.axay.kspigot.languageextensions.applyIfNotNull
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.SoundCategory
|
||||
|
Reference in New Issue
Block a user