Api auth in progress
This commit is contained in:
@@ -9,12 +9,11 @@ import javax.servlet.http.HttpServletResponse
|
||||
interface ApiHandler {
|
||||
|
||||
fun route(request: HttpServletRequest, response: HttpServletResponse) =
|
||||
// for now, only get() needed the response object ; other methods shall be reengineered as well if needed
|
||||
when (request.method) {
|
||||
"GET" -> get(request, response)
|
||||
"POST" -> post(request)
|
||||
"PUT" -> put(request)
|
||||
"DELETE" -> delete(request)
|
||||
"POST" -> post(request, response)
|
||||
"PUT" -> put(request, response)
|
||||
"DELETE" -> delete(request, response)
|
||||
else -> notImplemented()
|
||||
}
|
||||
|
||||
@@ -22,15 +21,15 @@ interface ApiHandler {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
fun post(request: HttpServletRequest): Json {
|
||||
fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
fun put(request: HttpServletRequest): Json {
|
||||
fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
fun delete(request: HttpServletRequest): Json {
|
||||
fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,15 @@
|
||||
package org.jeudego.pairgoth.api
|
||||
|
||||
class AuthChallenge {
|
||||
companion object {
|
||||
private val validChars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
private fun randomString(length: Int) = CharArray(length) { validChars.random() }.concatToString()
|
||||
private val lifespan = 30000L
|
||||
}
|
||||
private val _value = randomString(64)
|
||||
private val _gen = System.currentTimeMillis()
|
||||
|
||||
val value get() =
|
||||
if (System.currentTimeMillis() - _gen > lifespan) null
|
||||
else _value
|
||||
}
|
@@ -4,7 +4,6 @@ import com.republicate.kson.Json
|
||||
import com.republicate.kson.toJsonArray
|
||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||
import org.jeudego.pairgoth.model.Game
|
||||
import org.jeudego.pairgoth.model.PairingType
|
||||
import org.jeudego.pairgoth.model.getID
|
||||
import org.jeudego.pairgoth.model.toID
|
||||
import org.jeudego.pairgoth.model.toJson
|
||||
@@ -33,7 +32,7 @@ object PairingHandler: PairgothApiHandler {
|
||||
)
|
||||
}
|
||||
|
||||
override fun post(request: HttpServletRequest): Json {
|
||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
if (round > tournament.lastRound() + 1) badRequest("invalid round: previous round has not been played")
|
||||
@@ -63,7 +62,7 @@ object PairingHandler: PairgothApiHandler {
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun put(request: HttpServletRequest): Json {
|
||||
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
// only allow last round (if players have not been paired in the last round, it *may* be possible to be more laxist...)
|
||||
@@ -139,7 +138,7 @@ object PairingHandler: PairgothApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(request: HttpServletRequest): Json {
|
||||
override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
// only allow last round (if players have not been paired in the last round, it *may* be possible to be more laxist...)
|
||||
|
@@ -19,7 +19,7 @@ object PlayerHandler: PairgothApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
override fun post(request: HttpServletRequest): Json {
|
||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val payload = getObjectPayload(request)
|
||||
val player = Player.fromJson(payload)
|
||||
@@ -28,7 +28,7 @@ object PlayerHandler: PairgothApiHandler {
|
||||
return Json.Object("success" to true, "id" to player.id)
|
||||
}
|
||||
|
||||
override fun put(request: HttpServletRequest): Json {
|
||||
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||
val player = tournament.players[id] ?: badRequest("invalid player id")
|
||||
@@ -39,7 +39,7 @@ object PlayerHandler: PairgothApiHandler {
|
||||
return Json.Object("success" to true)
|
||||
}
|
||||
|
||||
override fun delete(request: HttpServletRequest): Json {
|
||||
override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||
tournament.players.remove(id) ?: badRequest("invalid player id")
|
||||
|
@@ -18,7 +18,7 @@ object ResultsHandler: PairgothApiHandler {
|
||||
return games.map { it.toJson() }.toJsonArray()
|
||||
}
|
||||
|
||||
override fun put(request: HttpServletRequest): Json {
|
||||
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||
val payload = getObjectPayload(request)
|
||||
|
@@ -19,7 +19,7 @@ object TeamHandler: PairgothApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
override fun post(request: HttpServletRequest): Json {
|
||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
||||
val payload = getObjectPayload(request)
|
||||
@@ -29,7 +29,7 @@ object TeamHandler: PairgothApiHandler {
|
||||
return Json.Object("success" to true, "id" to team.id)
|
||||
}
|
||||
|
||||
override fun put(request: HttpServletRequest): Json {
|
||||
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||
@@ -41,7 +41,7 @@ object TeamHandler: PairgothApiHandler {
|
||||
return Json.Object("success" to true)
|
||||
}
|
||||
|
||||
override fun delete(request: HttpServletRequest): Json {
|
||||
override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector")
|
||||
|
@@ -0,0 +1,43 @@
|
||||
package org.jeudego.pairgoth.api
|
||||
|
||||
import com.republicate.kson.Json
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import javax.servlet.http.HttpSession
|
||||
|
||||
class TokenHandler: ApiHandler {
|
||||
companion object {
|
||||
const val AUTH_KEY = "pairgoth-auth"
|
||||
const val CHALLENGE_KEY = "pairgoth-challenge"
|
||||
}
|
||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||
val auth = request.session.getAttribute(AUTH_KEY) as String?
|
||||
if (auth == null) {
|
||||
failed(request.session, response)
|
||||
return null
|
||||
} else {
|
||||
return Json.Object(
|
||||
"success" to true,
|
||||
"auth" to auth
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val auth = getObjectPayload(request)
|
||||
val answer = auth.getString("answer")
|
||||
if (answer == null) {
|
||||
failed(request.session, response)
|
||||
} else {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun failed(session: HttpSession, response: HttpServletResponse) {
|
||||
val challenge = AuthChallenge()
|
||||
session.setAttribute(CHALLENGE_KEY, challenge)
|
||||
response.addHeader("WWW-Authenticate", challenge.value)
|
||||
response.status = HttpServletResponse.SC_UNAUTHORIZED
|
||||
response.writer.println(Json.Object("status" to "failed"))
|
||||
}
|
||||
}
|
@@ -36,7 +36,7 @@ object TournamentHandler: PairgothApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
override fun post(request: HttpServletRequest): Json {
|
||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = when (val payload = request.getAttribute(PAYLOAD_KEY)) {
|
||||
is Json.Object -> Tournament.fromJson(getObjectPayload(request))
|
||||
is Element -> OpenGotha.import(payload)
|
||||
@@ -47,7 +47,7 @@ object TournamentHandler: PairgothApiHandler {
|
||||
return Json.Object("success" to true, "id" to tournament.id)
|
||||
}
|
||||
|
||||
override fun put(request: HttpServletRequest): Json {
|
||||
override fun put(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
// BC TODO - some checks are needed here (cannot lower rounds number if games have been played in removed rounds, for instance)
|
||||
val tournament = getTournament(request)
|
||||
val payload = getObjectPayload(request)
|
||||
@@ -67,7 +67,7 @@ object TournamentHandler: PairgothApiHandler {
|
||||
return Json.Object("success" to true)
|
||||
}
|
||||
|
||||
override fun delete(request: HttpServletRequest): Json {
|
||||
override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||
val tournament = getTournament(request)
|
||||
Store.deleteTournament(tournament)
|
||||
tournament.dispatchEvent(TournamentDeleted, Json.Object("id" to tournament.id))
|
||||
|
@@ -0,0 +1,57 @@
|
||||
package org.jeudego.pairgoth.server
|
||||
|
||||
import java.nio.charset.Charset
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.Cipher.DECRYPT_MODE
|
||||
import javax.crypto.Cipher.ENCRYPT_MODE
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
/**
|
||||
* Basic AES encryption. Please note that it uses the ECB block mode, which has the advantage
|
||||
* to not require random bytes, thus providing some *persistence* for the encrypted data, but
|
||||
* at the expense of some security weaknesses. The purpose here is just to encrypt temporary
|
||||
* session ids in URLs, not to protect state secrets.
|
||||
*/
|
||||
class AESCryptograph : Cryptograph {
|
||||
|
||||
override fun init(key: String) {
|
||||
val bytes = key.toByteArray(Charset.defaultCharset())
|
||||
if (bytes.size < 16) {
|
||||
throw Error("not enough secret bytes")
|
||||
}
|
||||
val secret: SecretKey = SecretKeySpec(bytes, 0, 16, ALGORITHM)
|
||||
try {
|
||||
encrypt.init(ENCRYPT_MODE, secret)
|
||||
decrypt.init(DECRYPT_MODE, secret)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("cyptograph initialization failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun encrypt(str: String): ByteArray {
|
||||
return try {
|
||||
encrypt.doFinal(str.toByteArray(Charset.defaultCharset()))
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("encryption failed failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun decrypt(bytes: ByteArray): String {
|
||||
return try {
|
||||
String(decrypt.doFinal(bytes), Charset.defaultCharset())
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException("encryption failed failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
private var encrypt = Cipher.getInstance(CIPHER)
|
||||
private var decrypt = Cipher.getInstance(CIPHER)
|
||||
|
||||
companion object {
|
||||
private val CIPHER = "AES/ECB/PKCS5Padding"
|
||||
private val ALGORITHM = "AES"
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package org.jeudego.pairgoth.server
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Cryptograph - used to encrypt and decrypt strings.
|
||||
*
|
||||
*/
|
||||
interface Cryptograph : Serializable {
|
||||
/**
|
||||
* init.
|
||||
* @param random random string
|
||||
*/
|
||||
fun init(random: String)
|
||||
|
||||
/**
|
||||
* encrypt.
|
||||
* @param str string to encrypt
|
||||
* @return encrypted string
|
||||
*/
|
||||
fun encrypt(str: String): ByteArray
|
||||
|
||||
/**
|
||||
* decrypt.
|
||||
* @param bytes to decrypt
|
||||
* @return decrypted string
|
||||
*/
|
||||
fun decrypt(bytes: ByteArray): String
|
||||
}
|
@@ -55,7 +55,7 @@ abstract class OAuthHelper {
|
||||
|
||||
companion object {
|
||||
protected var logger: Logger = LoggerFactory.getLogger("oauth")
|
||||
private const val salt = "0efd28fb53cbac42"
|
||||
private const val salt = "0efd28fb53cbac42"
|
||||
private val sessionIdCrypto: Cryptograph = AESCryptograph().apply {
|
||||
init(salt)
|
||||
}
|
||||
|
Reference in New Issue
Block a user