Improve Error handling/messages (#82)

* Add image error fields

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Update models

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Improve error handling/messages

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Minor reformat

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Improve error handling

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add base trait

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Decouple data access methods
Improve error handling

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Minor reformat

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add pattern matching to handle error
Remove try except blocks

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>
This commit is contained in:
Rahul Rudragoudar 2021-04-16 23:22:21 +05:30 committed by GitHub
parent 3a1b01688a
commit 43331f8dd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 205 additions and 138 deletions

View File

@ -25,7 +25,7 @@ class BackgroundTask(throttle: Int, timeLimit: Int) {
Captcha.generateChallenge(Parameters("medium", "image/png", "text", Option(Size(0, 0))))
throttleIn -= 1
}
} catch { case exception: Exception => println(exception.getStackTrace()) }
} catch { case exception: Exception => println(exception) }
}
}

View File

@ -5,34 +5,59 @@ import java.util.UUID
import java.io.ByteArrayInputStream
import lc.database.Statements
import lc.core.CaptchaProviders
import lc.captchas.interfaces.ChallengeProvider
import lc.captchas.interfaces.Challenge
import java.sql.Blob
object Captcha {
def getCaptcha(id: Id): Array[Byte] = {
var image: Array[Byte] = null
try {
val imagePstmt = Statements.tlStmts.get.imagePstmt
imagePstmt.setString(1, id.id)
val rs: ResultSet = imagePstmt.executeQuery()
if (rs.next()) {
val blob = rs.getBlob("image")
def getCaptcha(id: Id): Either[Error, Image] = {
val blob = getImage(id.id)
blob match {
case Some(value) => {
if (blob != null) {
image = blob.getBytes(1, blob.length().toInt)
Right(Image(value.getBytes(1, value.length().toInt)))
} else {
Left(Error(ErrorMessageEnum.IMG_MISSING.toString))
}
}
image
} catch {
case e: Exception =>
println(e)
image
case None => Left(Error(ErrorMessageEnum.IMG_NOT_FOUND.toString))
}
}
def generateChallenge(param: Parameters): Int = {
private def getImage(id: String): Option[Blob] = {
val imagePstmt = Statements.tlStmts.get.imagePstmt
imagePstmt.setString(1, id)
val rs: ResultSet = imagePstmt.executeQuery()
if (rs.next()) {
Some(rs.getBlob("image"))
} else {
None
}
}
def generateChallenge(param: Parameters): Option[Int] = {
val provider = CaptchaProviders.getProvider(param)
val providerId = provider.getId()
val challenge = provider.returnChallenge()
provider match {
case Some(value) => {
val providerId = value.getId()
val challenge = value.returnChallenge()
val blob = new ByteArrayInputStream(challenge.content)
val token = insertCaptcha(value, challenge, providerId, param, blob)
// println("Added new challenge: " + token.toString)
token.map(_.toInt)
}
case None => None
}
}
private def insertCaptcha(
provider: ChallengeProvider,
challenge: Challenge,
providerId: String,
param: Parameters,
blob: ByteArrayInputStream
): Option[Int] = {
val insertPstmt = Statements.tlStmts.get.insertPstmt
insertPstmt.setString(1, provider.getId)
insertPstmt.setString(2, challenge.secret)
@ -43,61 +68,63 @@ object Captcha {
insertPstmt.setBlob(7, blob)
insertPstmt.executeUpdate()
val rs: ResultSet = insertPstmt.getGeneratedKeys()
val token = if (rs.next()) {
rs.getInt("token")
if (rs.next()) {
Some(rs.getInt("token"))
} else {
None
}
// println("Added new challenge: " + token.toString)
token.asInstanceOf[Int]
}
val allowedInputType = Config.allowedInputType
val allowedLevels = Config.allowedLevels
val allowedMedia = Config.allowedMedia
private def validateParam(param: Parameters): Boolean = {
if (
allowedLevels.contains(param.level) &&
allowedMedia.contains(param.media) &&
allowedInputType.contains(param.input_type)
)
return true
else
return false
private def validateParam(param: Parameters): Array[String] = {
var invalid_params = Array[String]()
if (!allowedLevels.contains(param.level)) invalid_params :+= "level"
if (!allowedMedia.contains(param.media)) invalid_params :+= "media"
if (!allowedInputType.contains(param.input_type)) invalid_params :+= "input_type"
invalid_params
}
def getChallenge(param: Parameters): ChallengeResult = {
try {
def getChallenge(param: Parameters): Either[Error, Id] = {
val validParam = validateParam(param)
if (validParam) {
if (validParam.isEmpty) {
val tokenOpt = getToken(param)
val token = tokenOpt.orElse(generateChallenge(param))
token match {
case Some(value) => {
val uuid = getUUID(value)
updateAttempted(uuid)
Right(Id(uuid))
}
case None => {
Left(Error(ErrorMessageEnum.NO_CAPTCHA.toString))
}
}
} else {
Left(Error(ErrorMessageEnum.INVALID_PARAM.toString + " => " + validParam.mkString(", ")))
}
}
private def getToken(param: Parameters): Option[Int] = {
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
tokenPstmt.setString(1, param.level)
tokenPstmt.setString(2, param.media)
tokenPstmt.setString(3, param.input_type)
val rs = tokenPstmt.executeQuery()
val tokenOpt = if (rs.next()) {
if (rs.next()) {
Some(rs.getInt("token"))
} else {
None
}
}
private def updateAttempted(uuid: String): Unit = {
val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt
val token = tokenOpt.getOrElse(generateChallenge(param))
val result = if (token != -1) {
val uuid = getUUID(token)
updateAttemptedPstmt.setString(1, uuid)
updateAttemptedPstmt.executeUpdate()
Id(uuid)
} else {
Error(ErrorMessageEnum.NO_CAPTCHA.toString)
}
result
} else {
Error(ErrorMessageEnum.INVALID_PARAM.toString)
}
} catch {
case e: Exception =>
println(e)
Error(ErrorMessageEnum.SMW.toString)
}
}
private def getUUID(id: Int): String = {
@ -109,24 +136,38 @@ object Captcha {
uuid
}
def checkAnswer(answer: Answer): Result = {
def checkAnswer(answer: Answer): Either[Error, Success] = {
val challenge = getSecret(answer.id)
challenge match {
case None => Right(Success(ResultEnum.EXPIRED.toString))
case Some(value) => {
val (provider, secret) = value
val check = CaptchaProviders.getProviderById(provider).checkAnswer(secret, answer.answer)
deleteCaptcha(answer.id)
val result = if (check) ResultEnum.TRUE.toString else ResultEnum.FALSE.toString
Right(Success(result))
}
}
}
private def getSecret(id: String): Option[(String, String)] = {
val selectPstmt = Statements.tlStmts.get.selectPstmt
selectPstmt.setInt(1, Config.captchaExpiryTimeLimit)
selectPstmt.setString(2, answer.id)
selectPstmt.setString(2, id)
val rs: ResultSet = selectPstmt.executeQuery()
val psOpt = if (rs.first()) {
if (rs.first()) {
val secret = rs.getString("secret")
val provider = rs.getString("provider")
val check = CaptchaProviders.getProviderById(provider).checkAnswer(secret, answer.answer)
val result = if (check) ResultEnum.TRUE.toString else ResultEnum.FALSE.toString
result
Some(provider, secret)
} else {
ResultEnum.EXPIRED.toString
None
}
}
private def deleteCaptcha(id: String): Unit = {
val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt
deleteAnswerPstmt.setString(1, answer.id)
deleteAnswerPstmt.setString(1, id)
deleteAnswerPstmt.executeUpdate()
Result(psOpt)
}
def display(): Unit = {

View File

@ -38,6 +38,8 @@ object ErrorMessageEnum extends Enumeration {
val SMW: Value = Value("Oops, something went worng!")
val INVALID_PARAM: Value = Value("Parameters invalid or missing")
val NO_CAPTCHA: Value = Value("No captcha for the provided parameters")
val IMG_MISSING: Value = Value("Image missing")
val IMG_NOT_FOUND: Value = Value("Image not found")
val NO_CAPTCHA: Value = Value("No captcha for the provided parameters. Change config options.")
val BAD_METHOD: Value = Value("Bad request method")
}

View File

@ -12,7 +12,7 @@ object CaptchaProviders {
"GifCaptcha" -> new GifCaptcha,
"ShadowTextCaptcha" -> new ShadowTextCaptcha,
"RainDropsCaptcha" -> new RainDropsCP,
"DebugCaptcha" -> new DebugCaptcha,
"DebugCaptcha" -> new DebugCaptcha
//"LabelCaptcha" -> new LabelCaptcha
)
@ -55,13 +55,16 @@ object CaptchaProviders {
providerFilter
}
def getProvider(param: Parameters): ChallengeProvider = {
def getProvider(param: Parameters): Option[ChallengeProvider] = {
val providerConfig = filterProviderByParam(param).toList
if (providerConfig.length == 0) throw new NoSuchElementException(ErrorMessageEnum.NO_CAPTCHA.toString)
if (providerConfig.length > 0) {
val randomIndex = getNextRandomInt(providerConfig.length)
val providerIndex = providerConfig(randomIndex)._1
val selectedProvider = providers(providerIndex)
selectedProvider.configure(providerConfig(randomIndex)._2)
selectedProvider
Some(selectedProvider)
} else {
None
}
}
}

View File

@ -1,13 +1,16 @@
package lc.core
sealed trait ChallengeResult
import org.json4s.jackson.Serialization.write
import lc.core.Config.formats
trait ByteConvert { def toBytes(): Array[Byte] }
case class Size(height: Int, width: Int)
case class Parameters(level: String, media: String, input_type: String, size: Option[Size])
case class Id(id: String) extends ChallengeResult
case class Image(image: Array[Byte]) extends ChallengeResult
case class Id(id: String) extends ByteConvert { def toBytes(): Array[Byte] = { write(this).getBytes } }
case class Image(image: Array[Byte]) extends ByteConvert { def toBytes(): Array[Byte] = { image } }
case class Answer(answer: String, id: String)
case class Result(result: String)
case class Error(message: String) extends ChallengeResult
case class Success(result: String) extends ByteConvert { def toBytes(): Array[Byte] = { write(this).getBytes } }
case class Error(message: String) extends ByteConvert { def toBytes(): Array[Byte] = { write(this).getBytes } }
case class Response(statusCode: Int, message: Array[Byte])
case class CaptchaConfig(
name: String,

View File

@ -1,18 +1,16 @@
package lc.server
import org.json4s.DefaultFormats
import org.json4s.jackson.JsonMethods.parse
import org.json4s.jackson.Serialization.write
import lc.core.Captcha
import lc.core.ErrorMessageEnum
import lc.core.{Parameters, Id, Answer, Response}
import lc.core.{Parameters, Id, Answer, Response, Error, ByteConvert}
import org.json4s.JsonAST.JValue
import com.sun.net.httpserver.{HttpServer, HttpExchange}
import java.net.InetSocketAddress
import lc.core.Config.formats
class Server(port: Int) {
implicit val formats: DefaultFormats.type = DefaultFormats
val server: HttpServer = HttpServer.create(new InetSocketAddress(port), 32)
server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool())
@ -24,14 +22,19 @@ class Server(port: Int) {
}
private val eqPattern = java.util.regex.Pattern.compile("=")
private def getPathParameter(ex: HttpExchange): String = {
private def getPathParameter(ex: HttpExchange): Either[String, String] = {
try {
val query = ex.getRequestURI.getQuery
eqPattern.split(query)(1)
val param = eqPattern.split(query)
if (param(0) == "id") {
Right(param(1))
} else {
Left(ErrorMessageEnum.INVALID_PARAM.toString + "=> id")
}
} catch {
case exception: ArrayIndexOutOfBoundsException => {
println(exception.getStackTrace)
throw new Exception(ErrorMessageEnum.INVALID_PARAM.toString)
println(exception)
Left(ErrorMessageEnum.INVALID_PARAM.toString + "=> id")
}
}
}
@ -44,15 +47,25 @@ class Server(port: Int) {
}
private def getException(exception: Exception): Response = {
println(exception.printStackTrace)
val message = ("message" -> exception.getMessage)
val messageByte = write(message).getBytes
Response(500, messageByte)
println(exception)
val message = Error(exception.getMessage)
Response(500, message.toBytes())
}
private def getBadRequestError(): Response = {
val message = ("message" -> ErrorMessageEnum.BAD_METHOD.toString)
Response(405, write(message).getBytes)
val message = Error(ErrorMessageEnum.BAD_METHOD.toString)
Response(405, message.toBytes())
}
private def getResponse(response: Either[Error, ByteConvert]): Response = {
response match {
case Right(value) => {
Response(200, value.toBytes())
}
case Left(value) => {
Response(500, value.toBytes())
}
}
}
private def makeApiWorker(path: String, f: (String, HttpExchange) => Response): Unit = {
@ -85,7 +98,7 @@ class Server(port: Int) {
val json = getRequestJson(ex)
val param = json.extract[Parameters]
val id = Captcha.getChallenge(param)
Response(200, write(id).getBytes)
getResponse(id)
} else {
getBadRequestError()
}
@ -97,9 +110,14 @@ class Server(port: Int) {
(method: String, ex: HttpExchange) => {
if (method == "GET") {
val param = getPathParameter(ex)
val id = Id(param)
val image = Captcha.getCaptcha(id)
Response(200, image)
val result = param match {
case Right(value) => {
val id = Id(value)
Captcha.getCaptcha(id)
}
case Left(value) => Left(Error(value))
}
getResponse(result)
} else {
getBadRequestError()
}
@ -113,7 +131,7 @@ class Server(port: Int) {
val json = getRequestJson(ex)
val answer = json.extract[Answer]
val result = Captcha.checkAnswer(answer)
Response(200, write(result).getBytes)
getResponse(result)
} else {
getBadRequestError()
}