diff --git a/src/main/scala/lc/background/taskThread.scala b/src/main/scala/lc/background/taskThread.scala index e9a9177..33ae997 100644 --- a/src/main/scala/lc/background/taskThread.scala +++ b/src/main/scala/lc/background/taskThread.scala @@ -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) } } } diff --git a/src/main/scala/lc/core/captcha.scala b/src/main/scala/lc/core/captcha.scala index 146761b..385c6b1 100644 --- a/src/main/scala/lc/core/captcha.scala +++ b/src/main/scala/lc/core/captcha.scala @@ -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() - val blob = new ByteArrayInputStream(challenge.content) + 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,63 +68,65 @@ 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 { - val validParam = validateParam(param) - if (validParam) { - 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()) { - Some(rs.getInt("token")) - } else { - None + def getChallenge(param: Parameters): Either[Error, Id] = { + val validParam = validateParam(param) + 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)) } - 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) + case None => { + Left(Error(ErrorMessageEnum.NO_CAPTCHA.toString)) } - result - } else { - Error(ErrorMessageEnum.INVALID_PARAM.toString) } - } catch { - case e: Exception => - println(e) - Error(ErrorMessageEnum.SMW.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() + if (rs.next()) { + Some(rs.getInt("token")) + } else { + None + } + } + + private def updateAttempted(uuid: String): Unit = { + val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt + updateAttemptedPstmt.setString(1, uuid) + updateAttemptedPstmt.executeUpdate() + } + private def getUUID(id: Int): String = { val uuid = UUID.randomUUID().toString val mapPstmt = Statements.tlStmts.get.mapPstmt @@ -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 = { diff --git a/src/main/scala/lc/core/captchaFields.scala b/src/main/scala/lc/core/captchaFields.scala index 9a6e223..80e691d 100644 --- a/src/main/scala/lc/core/captchaFields.scala +++ b/src/main/scala/lc/core/captchaFields.scala @@ -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") } diff --git a/src/main/scala/lc/core/captchaProviders.scala b/src/main/scala/lc/core/captchaProviders.scala index 1174ade..8a61841 100644 --- a/src/main/scala/lc/core/captchaProviders.scala +++ b/src/main/scala/lc/core/captchaProviders.scala @@ -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) - val randomIndex = getNextRandomInt(providerConfig.length) - val providerIndex = providerConfig(randomIndex)._1 - val selectedProvider = providers(providerIndex) - selectedProvider.configure(providerConfig(randomIndex)._2) - selectedProvider + if (providerConfig.length > 0) { + val randomIndex = getNextRandomInt(providerConfig.length) + val providerIndex = providerConfig(randomIndex)._1 + val selectedProvider = providers(providerIndex) + selectedProvider.configure(providerConfig(randomIndex)._2) + Some(selectedProvider) + } else { + None + } } } diff --git a/src/main/scala/lc/core/config.scala b/src/main/scala/lc/core/config.scala index 233108f..a0adc77 100644 --- a/src/main/scala/lc/core/config.scala +++ b/src/main/scala/lc/core/config.scala @@ -49,42 +49,42 @@ object Config { val allowedInputType: Set[String] = captchaConfig.flatMap(_.allowedInputType).toSet private def getDefaultConfig(): String = { - val defaultConfigMap = + val defaultConfigMap = (AttributesEnum.RANDOM_SEED.toString -> 20) ~ - (AttributesEnum.PORT.toString -> 8888) ~ - (AttributesEnum.CAPTCHA_EXPIRY_TIME_LIMIT.toString -> 5) ~ - (AttributesEnum.THROTTLE.toString -> 10) ~ - (AttributesEnum.THREAD_DELAY.toString -> 2) ~ - ("captchas" -> List( - ( - (AttributesEnum.NAME.toString -> "FilterChallenge") ~ - (ParametersEnum.ALLOWEDLEVELS.toString -> List("medium", "hard")) ~ - (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ - (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ - (AttributesEnum.CONFIG.toString -> JObject()) - ), - ( - (AttributesEnum.NAME.toString -> "GifCaptcha") ~ - (ParametersEnum.ALLOWEDLEVELS.toString -> List("hard")) ~ - (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ - (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ - (AttributesEnum.CONFIG.toString -> JObject()) - ), - ( - (AttributesEnum.NAME.toString -> "ShadowTextCaptcha") ~ - (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy")) ~ - (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ - (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ - (AttributesEnum.CONFIG.toString -> JObject()) - ), - ( - (AttributesEnum.NAME.toString -> "RainDropsCaptcha") ~ - (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy", "medium")) ~ - (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ - (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ - (AttributesEnum.CONFIG.toString -> JObject()) - ) - )) + (AttributesEnum.PORT.toString -> 8888) ~ + (AttributesEnum.CAPTCHA_EXPIRY_TIME_LIMIT.toString -> 5) ~ + (AttributesEnum.THROTTLE.toString -> 10) ~ + (AttributesEnum.THREAD_DELAY.toString -> 2) ~ + ("captchas" -> List( + ( + (AttributesEnum.NAME.toString -> "FilterChallenge") ~ + (ParametersEnum.ALLOWEDLEVELS.toString -> List("medium", "hard")) ~ + (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ + (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (AttributesEnum.CONFIG.toString -> JObject()) + ), + ( + (AttributesEnum.NAME.toString -> "GifCaptcha") ~ + (ParametersEnum.ALLOWEDLEVELS.toString -> List("hard")) ~ + (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ + (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (AttributesEnum.CONFIG.toString -> JObject()) + ), + ( + (AttributesEnum.NAME.toString -> "ShadowTextCaptcha") ~ + (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy")) ~ + (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/png")) ~ + (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (AttributesEnum.CONFIG.toString -> JObject()) + ), + ( + (AttributesEnum.NAME.toString -> "RainDropsCaptcha") ~ + (ParametersEnum.ALLOWEDLEVELS.toString -> List("easy", "medium")) ~ + (ParametersEnum.ALLOWEDMEDIA.toString -> List("image/gif")) ~ + (ParametersEnum.ALLOWEDINPUTTYPE.toString -> List("text")) ~ + (AttributesEnum.CONFIG.toString -> JObject()) + ) + )) pretty(render(defaultConfigMap)) } diff --git a/src/main/scala/lc/core/models.scala b/src/main/scala/lc/core/models.scala index 834607f..b06a1aa 100644 --- a/src/main/scala/lc/core/models.scala +++ b/src/main/scala/lc/core/models.scala @@ -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, diff --git a/src/main/scala/lc/server/Server.scala b/src/main/scala/lc/server/Server.scala index 26089f2..b5c2848 100644 --- a/src/main/scala/lc/server/Server.scala +++ b/src/main/scala/lc/server/Server.scala @@ -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() }