mirror of
https://github.com/yairm210/Unciv.git
synced 2025-08-03 12:37:42 -04:00
more detailed password authentication status (#13198)
* more detailed password authentication status * fix compile errors * resole some concerns * remove stray `Concurrency.run { }` * fix no space at end error * modify current behavior of UncivServer.jar * refactor code * accept idea suggestions * rename `authString` -> `authHeader` for clarity * send `Bad Request` instead of `Unauthorized` if `Authorization` header is not present * run `checkAuthStatus()` on a separate thread * fix no space at end test failure
This commit is contained in:
parent
a5a148cc51
commit
1bced0df7b
@ -727,7 +727,11 @@ Password must be at least 6 characters long =
|
||||
Failed to set password! =
|
||||
Password set successfully for server [serverURL] =
|
||||
Password =
|
||||
Your userId is password secured =
|
||||
Validating your authentication status... =
|
||||
Your current password was rejected from the server =
|
||||
You userId is unregistered! Set password to secure your userId =
|
||||
Your current password has been succesfully verified =
|
||||
Your authentication status could not be determined =
|
||||
Set a password to secure your userId =
|
||||
Authenticate =
|
||||
This server does not support authentication =
|
||||
|
@ -85,6 +85,10 @@ class ApiV2FileStorageEmulator(private val api: ApiV2) : FileStorage {
|
||||
return runBlocking { api.auth.loginOnly(userId, password) }
|
||||
}
|
||||
|
||||
override fun checkAuthStatus(userId: String, password: String): AuthStatus {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setPassword(newPassword: String): Boolean {
|
||||
return runBlocking { api.account.setPassword(newPassword, suppress = true) }
|
||||
}
|
||||
|
@ -107,6 +107,10 @@ object DropBox: FileStorage {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
override fun checkAuthStatus(userId: String, password: String): AuthStatus {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
override fun setPassword(newPassword: String): Boolean {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
@ -13,6 +13,13 @@ interface FileMetaData {
|
||||
fun getLastModified(): Date?
|
||||
}
|
||||
|
||||
enum class AuthStatus {
|
||||
UNAUTHORIZED,
|
||||
UNREGISTERED,
|
||||
VERIFIED,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
interface FileStorage {
|
||||
/**
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
@ -45,4 +52,6 @@ interface FileStorage {
|
||||
* @throws MultiplayerAuthException if the authentication failed
|
||||
*/
|
||||
fun setPassword(newPassword: String): Boolean
|
||||
|
||||
fun checkAuthStatus(userId: String, password: String): AuthStatus
|
||||
}
|
||||
|
@ -76,6 +76,21 @@ object UncivServerFileStorage : FileStorage {
|
||||
return authenticated
|
||||
}
|
||||
|
||||
override fun checkAuthStatus(userId: String, password: String): AuthStatus {
|
||||
var authStatus = AuthStatus.UNKNOWN
|
||||
val preEncodedAuthValue = "$userId:$password"
|
||||
authHeader = mapOf("Authorization" to "Basic ${Base64Coder.encodeString(preEncodedAuthValue)}")
|
||||
SimpleHttp.sendGetRequest("$serverUrl/auth", timeout = timeout, header = authHeader) { _, _, code ->
|
||||
authStatus = when (code) {
|
||||
200 -> AuthStatus.VERIFIED
|
||||
204 -> AuthStatus.UNREGISTERED
|
||||
401 -> AuthStatus.UNAUTHORIZED
|
||||
else -> AuthStatus.UNKNOWN
|
||||
}
|
||||
}
|
||||
return authStatus
|
||||
}
|
||||
|
||||
override fun setPassword(newPassword: String): Boolean {
|
||||
if (authHeader == null)
|
||||
return false
|
||||
|
@ -4,23 +4,23 @@ import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.files.IMediaFinder
|
||||
import com.unciv.logic.multiplayer.Multiplayer
|
||||
import com.unciv.logic.multiplayer.storage.AuthStatus
|
||||
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
|
||||
import com.unciv.logic.multiplayer.storage.MultiplayerAuthException
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.metadata.GameSettings.GameSetting
|
||||
import com.unciv.ui.components.widgets.UncivTextField
|
||||
import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.brighten
|
||||
import com.unciv.ui.components.extensions.format
|
||||
import com.unciv.ui.components.extensions.isEnabled
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.onChange
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.widgets.UncivTextField
|
||||
import com.unciv.ui.popups.AuthPopup
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.popups.options.SettingsSelect.SelectItem
|
||||
@ -163,14 +163,34 @@ private fun addMultiplayerServerOptions(
|
||||
serverIpTable.add("Set password".toLabel()).padTop(16f).colspan(2).row()
|
||||
serverIpTable.add(passwordTextField).colspan(2).growX().padBottom(8f).row()
|
||||
|
||||
// initially assume no password
|
||||
val authStatusLabel = "Set a password to secure your userId".toLabel()
|
||||
|
||||
if (settings.multiplayer.passwords.containsKey(settings.multiplayer.server)) {
|
||||
val userId = settings.multiplayer.userId
|
||||
val password = settings.multiplayer.passwords[settings.multiplayer.server] ?: ""
|
||||
|
||||
|
||||
authStatusLabel.setText("Validating your authentication status...")
|
||||
Concurrency.run {
|
||||
val authStatus = UncivGame.Current.onlineMultiplayer.multiplayerServer.fileStorage()
|
||||
.checkAuthStatus(userId, password)
|
||||
|
||||
val newAuthStatusText = when (authStatus) {
|
||||
AuthStatus.UNAUTHORIZED -> "Your current password was rejected from the server"
|
||||
AuthStatus.UNREGISTERED -> "You userId is unregistered! Set password to secure your userId"
|
||||
AuthStatus.VERIFIED -> "Your current password has been succesfully verified"
|
||||
AuthStatus.UNKNOWN -> "Your authentication status could not be determined"
|
||||
}
|
||||
|
||||
Concurrency.runOnGLThread {
|
||||
authStatusLabel.setText(newAuthStatusText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val passwordStatusTable = Table().apply {
|
||||
add(
|
||||
if (settings.multiplayer.passwords.containsKey(settings.multiplayer.server)) {
|
||||
"Your userId is password secured"
|
||||
} else {
|
||||
"Set a password to secure your userId"
|
||||
}.toLabel()
|
||||
)
|
||||
add(authStatusLabel)
|
||||
add(setPasswordButton.onClick {
|
||||
setPassword(passwordTextField.text, optionsPopup)
|
||||
}).padLeft(16f)
|
||||
|
@ -50,7 +50,7 @@ private class UncivServerRunner : CliktCommand() {
|
||||
help = "Enable Authentication"
|
||||
).flag("-no-auth", default = false)
|
||||
|
||||
private val IdentifyOperators by option(
|
||||
private val identifyOperators by option(
|
||||
"-i", "-Identify",
|
||||
envvar = "UncivServerIdentify",
|
||||
help = "Display each operation archive request IP to assist management personnel"
|
||||
@ -99,20 +99,18 @@ private class UncivServerRunner : CliktCommand() {
|
||||
return true
|
||||
|
||||
val (userId, password) = extractAuth(authString) ?: return false
|
||||
if (authMap[userId] == null || authMap[userId] == password)
|
||||
return true
|
||||
return false
|
||||
return authMap[userId] == null || authMap[userId] == password
|
||||
}
|
||||
|
||||
private fun extractAuth(authString: String?): Pair<String, String>? {
|
||||
private fun extractAuth(authHeader: String?): Pair<String, String>? {
|
||||
if (!authV1Enabled)
|
||||
return null
|
||||
|
||||
// If auth is enabled a auth string is required
|
||||
if (authString == null || !authString.startsWith("Basic "))
|
||||
// If auth is enabled an authorization header is required
|
||||
if (authHeader == null || !authHeader.startsWith("Basic "))
|
||||
return null
|
||||
|
||||
val decodedString = String(Base64.getDecoder().decode(authString.drop(6)))
|
||||
val decodedString = String(Base64.getDecoder().decode(authHeader.drop(6)))
|
||||
val splitAuthString = decodedString.split(":", limit=2)
|
||||
if (splitAuthString.size != 2)
|
||||
return null
|
||||
@ -136,11 +134,11 @@ private class UncivServerRunner : CliktCommand() {
|
||||
put("/files/{fileName}") {
|
||||
val fileName = call.parameters["fileName"] ?: throw Exception("No fileName!")
|
||||
|
||||
// If IdentifyOperators is enabled a Operator IP is displayed
|
||||
if (IdentifyOperators) {
|
||||
log.info("Receiving file: ${fileName} --Operation sourced from ${call.request.local.remoteHost}")
|
||||
// If IdentifyOperators is enabled an Operator IP is displayed
|
||||
if (identifyOperators) {
|
||||
log.info("Receiving file: $fileName --Operation sourced from ${call.request.local.remoteHost}")
|
||||
}else{
|
||||
log.info("Receiving file: ${fileName}")
|
||||
log.info("Receiving file: $fileName")
|
||||
}
|
||||
|
||||
val file = File(fileFolderName, fileName)
|
||||
@ -160,9 +158,9 @@ private class UncivServerRunner : CliktCommand() {
|
||||
get("/files/{fileName}") {
|
||||
val fileName = call.parameters["fileName"] ?: throw Exception("No fileName!")
|
||||
|
||||
// If IdentifyOperators is enabled a Operator IP is displayed
|
||||
if (IdentifyOperators) {
|
||||
log.info("File requested: ${fileName} --Operation sourced from ${call.request.local.remoteHost}")
|
||||
// If IdentifyOperators is enabled an Operator IP is displayed
|
||||
if (identifyOperators) {
|
||||
log.info("File requested: $fileName --Operation sourced from ${call.request.local.remoteHost}")
|
||||
}else{
|
||||
log.info("File requested: $fileName")
|
||||
}
|
||||
@ -170,10 +168,10 @@ private class UncivServerRunner : CliktCommand() {
|
||||
val file = File(fileFolderName, fileName)
|
||||
if (!file.exists()) {
|
||||
|
||||
// If IdentifyOperators is enabled a Operator IP is displayed
|
||||
if (IdentifyOperators) {
|
||||
log.info("File ${fileName} not found --Operation sourced from ${call.request.local.remoteHost}")
|
||||
}else{
|
||||
// If IdentifyOperators is enabled an Operator IP is displayed
|
||||
if (identifyOperators) {
|
||||
log.info("File $fileName not found --Operation sourced from ${call.request.local.remoteHost}")
|
||||
} else {
|
||||
log.info("File $fileName not found")
|
||||
}
|
||||
call.respond(HttpStatusCode.NotFound, "File does not exist")
|
||||
@ -186,11 +184,21 @@ private class UncivServerRunner : CliktCommand() {
|
||||
if (authV1Enabled) {
|
||||
get("/auth") {
|
||||
log.info("Received auth request from ${call.request.local.remoteHost}")
|
||||
val authHeader = call.request.headers["Authorization"]
|
||||
if (validateAuth(authHeader)) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
|
||||
val authHeader = call.request.headers["Authorization"] ?: run {
|
||||
call.respond(HttpStatusCode.BadRequest, "Missing authorization header!")
|
||||
return@get
|
||||
}
|
||||
|
||||
val (userId, password) = extractAuth(authHeader) ?: run {
|
||||
call.respond(HttpStatusCode.BadRequest, "Malformed authorization header!")
|
||||
return@get
|
||||
}
|
||||
|
||||
when (authMap[userId]) {
|
||||
null -> call.respond(HttpStatusCode.NoContent)
|
||||
password -> call.respond(HttpStatusCode.OK)
|
||||
else -> call.respond(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
put("/auth") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user