diff --git a/.gitignore b/.gitignore index 2bc6a5b..822b674 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ /project/** /target/ **__pycache__ +.bloop +.metals +.vscode # for various captcha /known/ diff --git a/config.json b/config.json new file mode 100644 index 0000000..ee7ed58 --- /dev/null +++ b/config.json @@ -0,0 +1,34 @@ +{ + "randomSeed": 20, + "captchaExpiryTimeLimit": 5, + "captchas":{ + "FilterChallenge":{ + "name": "FilterChallenge", + "supportedLevels":["medium", "hard"], + "supportedMedia":["image"], + "supportedinputType":["text"], + "config":{} + }, + "GifCaptcha":{ + "name": "GifCaptcha", + "supportedLevels":["hard"], + "supportedMedia":["gif"], + "supportedinputType":["text"], + "config":{} + }, + "ShadowTextCaptcha":{ + "name": "ShadowTextCaptcha", + "supportedLevels":["easy"], + "supportedMedia":["image"], + "supportedinputType":["text"], + "config": {} + }, + "RainDropsCaptcha":{ + "name": "RainDropsCaptcha", + "supportedLevels":["easy","medium"], + "supportedMedia":["image"], + "supportedinputType":["text"], + "config":{} + } + } +} \ No newline at end of file diff --git a/src/main/java/lc/FontFunCaptcha.java b/src/main/java/lc/captchas/FontFunCaptcha.java similarity index 94% rename from src/main/java/lc/FontFunCaptcha.java rename to src/main/java/lc/captchas/FontFunCaptcha.java index ce55c04..31b8a46 100644 --- a/src/main/java/lc/FontFunCaptcha.java +++ b/src/main/java/lc/captchas/FontFunCaptcha.java @@ -1,4 +1,4 @@ -package lc; +package lc.captchas; import javax.imageio.ImageIO; import java.awt.*; @@ -6,6 +6,9 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FilenameFilter; +import lc.captchas.interfaces.Challenge; +import lc.captchas.interfaces.ChallengeProvider; +import lc.misc.HelperFunctions; public class FontFunCaptcha implements ChallengeProvider{ diff --git a/src/main/java/lc/GifCaptcha.java b/src/main/java/lc/captchas/GifCaptcha.java similarity index 92% rename from src/main/java/lc/GifCaptcha.java rename to src/main/java/lc/captchas/GifCaptcha.java index 6e502be..2b23362 100644 --- a/src/main/java/lc/GifCaptcha.java +++ b/src/main/java/lc/captchas/GifCaptcha.java @@ -1,4 +1,4 @@ -package lc; +package lc.captchas; import java.awt.Font; import java.awt.Graphics2D; @@ -9,6 +9,10 @@ import java.io.IOException; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; import java.io.ByteArrayOutputStream; +import lc.captchas.interfaces.Challenge; +import lc.captchas.interfaces.ChallengeProvider; +import lc.misc.HelperFunctions; +import lc.misc.GifSequenceWriter; public class GifCaptcha implements ChallengeProvider{ diff --git a/src/main/java/lc/ShadowTextCaptcha.java b/src/main/java/lc/captchas/ShadowTextCaptcha.java similarity index 92% rename from src/main/java/lc/ShadowTextCaptcha.java rename to src/main/java/lc/captchas/ShadowTextCaptcha.java index a594211..0dbf3f7 100644 --- a/src/main/java/lc/ShadowTextCaptcha.java +++ b/src/main/java/lc/captchas/ShadowTextCaptcha.java @@ -1,4 +1,4 @@ -package lc; +package lc.captchas; import javax.imageio.ImageIO; import java.awt.Graphics2D; @@ -10,11 +10,14 @@ import java.awt.image.BufferedImage; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.io.ByteArrayOutputStream; +import lc.misc.HelperFunctions; +import lc.captchas.interfaces.Challenge; +import lc.captchas.interfaces.ChallengeProvider; public class ShadowTextCaptcha implements ChallengeProvider{ public String getId() { - return "ShadowText"; + return "ShadowTextCaptcha"; } public boolean checkAnswer(String secret, String answer) { diff --git a/src/main/java/lc/Challenge.java b/src/main/java/lc/captchas/interfaces/Challenge.java similarity index 92% rename from src/main/java/lc/Challenge.java rename to src/main/java/lc/captchas/interfaces/Challenge.java index ae6373b..6c6a560 100644 --- a/src/main/java/lc/Challenge.java +++ b/src/main/java/lc/captchas/interfaces/Challenge.java @@ -1,4 +1,4 @@ -package lc; +package lc.captchas.interfaces; public class Challenge { public final byte[] content; diff --git a/src/main/java/lc/ChallengeProvider.java b/src/main/java/lc/captchas/interfaces/ChallengeProvider.java similarity index 69% rename from src/main/java/lc/ChallengeProvider.java rename to src/main/java/lc/captchas/interfaces/ChallengeProvider.java index f377408..0e50210 100644 --- a/src/main/java/lc/ChallengeProvider.java +++ b/src/main/java/lc/captchas/interfaces/ChallengeProvider.java @@ -1,6 +1,6 @@ -package lc; +package lc.captchas.interfaces; -interface ChallengeProvider { +public interface ChallengeProvider { public String getId(); public Challenge returnChallenge(); public boolean checkAnswer(String secret, String answer); diff --git a/src/main/java/lc/GifSequenceWriter.java b/src/main/java/lc/misc/GifSequenceWriter.java similarity index 99% rename from src/main/java/lc/GifSequenceWriter.java rename to src/main/java/lc/misc/GifSequenceWriter.java index 0cf014a..0ebcc1e 100644 --- a/src/main/java/lc/GifSequenceWriter.java +++ b/src/main/java/lc/misc/GifSequenceWriter.java @@ -1,7 +1,7 @@ // This code was adapted from http://elliot.kroo.net/software/java/GifSequenceWriter/ // It was available under CC By 3.0 -package lc; +package lc.misc; import javax.imageio.*; import javax.imageio.metadata.*; import javax.imageio.stream.*; diff --git a/src/main/java/lc/HelperFunctions.java b/src/main/java/lc/misc/HelperFunctions.java similarity index 98% rename from src/main/java/lc/HelperFunctions.java rename to src/main/java/lc/misc/HelperFunctions.java index e3882ca..916c290 100644 --- a/src/main/java/lc/HelperFunctions.java +++ b/src/main/java/lc/misc/HelperFunctions.java @@ -1,4 +1,4 @@ -package lc; +package lc.misc; import java.awt.*; diff --git a/src/main/java/lc/HTTPServer.java b/src/main/java/lc/server/HTTPServer.java similarity index 99% rename from src/main/java/lc/HTTPServer.java rename to src/main/java/lc/server/HTTPServer.java index b7b300e..e558ec8 100644 --- a/src/main/java/lc/HTTPServer.java +++ b/src/main/java/lc/server/HTTPServer.java @@ -1,4 +1,4 @@ -package lc;/* +package lc.server;/* * Copyright © 2005-2018 Amichai Rothman * * This file is part of JLHTTP - the Java Lightweight HTTP Server. diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 96e64ac..5bfca8d 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -1,222 +1,20 @@ package lc -import com.sksamuel.scrimage._ -import java.io.ByteArrayInputStream -import java.util.concurrent._ -import java.util.UUID -import java.sql.{Blob, ResultSet} -import java.util.concurrent.atomic.AtomicInteger -import java.io._ -import java.sql.Statement +import org.json4s.DefaultFormats +import org.json4s.jackson.JsonMethods._ +import scala.io.Source.fromFile +import lc.database.Statements +import lc.core.{Captcha, CaptchaProviders} +import lc.server.Server +import lc.background.BackgroundTask -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) -case class Answer(answer: String, id: String) - -case class ProviderSecret(provider: String, secret: String) - -object CaptchaProviders { - val providers = Map( - "FilterChallenge" -> new FilterChallenge, - // "FontFunCaptcha" -> new FontFunCaptcha, - "GifCaptcha" -> new GifCaptcha, - "ShadowTextCaptcha" -> new ShadowTextCaptcha, - "RainDropsCaptcha" -> new RainDropsCP, - // "LabelCaptcha" -> new LabelCaptcha - ) - - def generateChallengeSamples() = { - providers.map {case (key, provider) => - (key, provider.returnChallenge()) - } - } -} - -class Statements(dbConn: DBConn) { - val insertPstmt = dbConn.con.prepareStatement("INSERT INTO challenge(id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS ) - val mapPstmt = dbConn.con.prepareStatement("INSERT INTO mapId(uuid, token, lastServed) VALUES (?, ?, CURRENT_TIMESTAMP)") - val selectPstmt = dbConn.con.prepareStatement("SELECT c.secret, c.provider FROM challenge c, mapId m WHERE m.token=c.token AND DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, m.lastServed)) > 0 AND m.uuid = ?") - val imagePstmt = dbConn.con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") - val updateAttemptedPstmt = dbConn.con.prepareStatement("UPDATE challenge SET attempted = attempted+1 WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ?)") - val tokenPstmt = dbConn.con.prepareStatement("SELECT token FROM challenge WHERE attempted < 10 ORDER BY RAND() LIMIT 1") - val deleteAnswerPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE uuid = ?") - val challengeGCPstmt = dbConn.con.prepareStatement("DELETE FROM challenge WHERE attempted >= 10 AND token NOT IN (SELECT token FROM mapId)") - val mapIdGCPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0") -} - -object Statements { - var dbConn: DBConn = _ - val tlStmts = ThreadLocal.withInitial(() => new Statements(dbConn)) -} - -class Captcha(throttle: Int, dbConn: DBConn) { - import CaptchaProviders._ - - private val stmt = dbConn.getStatement() - stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token int auto_increment, id varchar, secret varchar, provider varchar, contentType varchar, image blob, attempted int default 0, PRIMARY KEY(token))") - stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token int, lastServed timestamp, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token) ON DELETE CASCADE)") - - private val seed = System.currentTimeMillis.toString.substring(2,6).toInt - private val random = new scala.util.Random(seed) - - def getNextRandomInt(max: Int) = random.synchronized { - random.nextInt(max) - } - - def getProvider(): String = { - val keys = providers.keys - val providerIndex = keys.toVector(getNextRandomInt(keys.size)) - providerIndex - } - - def getCaptcha(id: Id): Array[Byte] = { - var image :Array[Byte] = null - var blob: Blob = null - try { - val imagePstmt = Statements.tlStmts.get.imagePstmt - imagePstmt.setString(1, id.id) - val rs: ResultSet = imagePstmt.executeQuery() - if(rs.next()){ - blob = rs.getBlob("image") - if(blob != null){ - image = blob.getBytes(1, blob.length().toInt) - } - } - image - } catch { case e: Exception => - println(e) - image - } - } - - private val uniqueIntCount = new AtomicInteger() - - def generateChallenge(param: Parameters): Int = { - //TODO: eval params to choose a provider - val providerMap = getProvider() - val provider = providers(providerMap) - val challenge = provider.returnChallenge() - val blob = new ByteArrayInputStream(challenge.content) - val insertPstmt = Statements.tlStmts.get.insertPstmt - insertPstmt.setString(1, provider.getId) - insertPstmt.setString(2, challenge.secret) - insertPstmt.setString(3, providerMap) - insertPstmt.setString(4, challenge.contentType) - insertPstmt.setBlob(5, blob) - insertPstmt.executeUpdate() - val rs: ResultSet = insertPstmt.getGeneratedKeys() - val token = if(rs.next()){ - rs.getInt("token") - } - println("Added new challenge: "+ token.toString) - token.asInstanceOf[Int] - } - - val task = new Runnable { - def run(): Unit = { - try { - - val mapIdGCPstmt = Statements.tlStmts.get.mapIdGCPstmt - mapIdGCPstmt.executeUpdate() - - val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt - challengeGCPstmt.executeUpdate() - - val imageNum = stmt.executeQuery("SELECT COUNT(*) AS total FROM challenge") - var throttleIn = (throttle*1.1).toInt - if(imageNum.next()) - throttleIn = (throttleIn-imageNum.getInt("total")) - while(0 < throttleIn){ - generateChallenge(Parameters("","","",Option(Size(0,0)))) - throttleIn -= 1 - } - } catch { case e: Exception => println(e) } - } - } - - def beginThread(delay: Int) : Unit = { - val ex = new ScheduledThreadPoolExecutor(1) - val thread = ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS) - } - - def getChallenge(param: Parameters): Id = { - try { - val tokenPstmt = Statements.tlStmts.get.tokenPstmt - val rs = tokenPstmt.executeQuery() - val tokenOpt = if(rs.next()) { - Some(rs.getInt("token")) - } else { - None - } - val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt - val uuid = getUUID(tokenOpt.getOrElse(generateChallenge(param))) - updateAttemptedPstmt.setString(1, uuid) - updateAttemptedPstmt.executeUpdate() - Id(uuid) - } catch {case e: Exception => - println(e) - Id(getUUID(-1)) - } - } - - def getUUID(id: Int): String = { - val uuid = UUID.randomUUID().toString - val mapPstmt = Statements.tlStmts.get.mapPstmt - mapPstmt.setString(1,uuid) - mapPstmt.setInt(2,id) - mapPstmt.executeUpdate() - uuid - } - - def checkAnswer(answer: Answer): String = { - val selectPstmt = Statements.tlStmts.get.selectPstmt - selectPstmt.setString(1, answer.id) - val rs: ResultSet = selectPstmt.executeQuery() - val psOpt = if (rs.first()) { - val secret = rs.getString("secret") - val provider = rs.getString("provider") - val check = providers(provider).checkAnswer(secret, answer.answer) - val result = if(check) "TRUE" else "FALSE" - result - } else { - "EXPIRED" - } - val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt - deleteAnswerPstmt.setString(1, answer.id) - deleteAnswerPstmt.executeUpdate() - psOpt - } - - def display(): Unit = { - val rs: ResultSet = stmt.executeQuery("SELECT * FROM challenge") - println("token\t\tid\t\tsecret\t\tattempted") - while(rs.next()) { - val token = rs.getInt("token") - val id = rs.getString("id") - val secret = rs.getString("secret") - val attempted = rs.getString("attempted") - println(s"${token}\t\t${id}\t\t${secret}\t\t${attempted}\n\n") - } - - val rss: ResultSet = stmt.executeQuery("SELECT * FROM mapId") - println("uuid\t\ttoken\t\tlastServed") - while(rss.next()){ - val uuid = rss.getString("uuid") - val token = rss.getInt("token") - val lastServed = rss.getTimestamp("lastServed") - println(s"${uuid}\t\t${token}\t\t${lastServed}\n\n") - } - } -} object LCFramework{ def main(args: scala.Array[String]) { - val dbConn = new DBConn() - Statements.dbConn = dbConn - val captcha = new Captcha(2, dbConn) - val server = new Server(8888, captcha, dbConn) - captcha.beginThread(2) + val captcha = new Captcha() + val server = new Server(8888, captcha) + val backgroudTask = new BackgroundTask(captcha, 10) + backgroudTask.beginThread(2) server.start() } } diff --git a/src/main/scala/lc/background/taskThread.scala b/src/main/scala/lc/background/taskThread.scala new file mode 100644 index 0000000..774607b --- /dev/null +++ b/src/main/scala/lc/background/taskThread.scala @@ -0,0 +1,38 @@ +package lc.background + +import lc.database.Statements +import java.util.concurrent.{ScheduledThreadPoolExecutor, TimeUnit} +import lc.core.Captcha +import lc.core.{Parameters, Size} + + +class BackgroundTask(captcha: Captcha, throttle: Int) { + + private val task = new Runnable { + def run(): Unit = { + try { + + val mapIdGCPstmt = Statements.tlStmts.get.mapIdGCPstmt + mapIdGCPstmt.executeUpdate() + + val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt + challengeGCPstmt.executeUpdate() + + val imageNum = Statements.tlStmts.get.getCountChallengeTable.executeQuery() + var throttleIn = (throttle*1.1).toInt + if(imageNum.next()) + throttleIn = (throttleIn-imageNum.getInt("total")) + while(0 < throttleIn){ + captcha.generateChallenge(Parameters("","","",Option(Size(0,0)))) + throttleIn -= 1 + } + } catch { case e: Exception => println(e) } + } + } + + def beginThread(delay: Int) : Unit = { + val ex = new ScheduledThreadPoolExecutor(1) + val thread = ex.scheduleWithFixedDelay(task, 1, delay, TimeUnit.SECONDS) + } + +} \ No newline at end of file diff --git a/src/main/scala/lc/FilterChallenge.scala b/src/main/scala/lc/captchas/FilterChallenge.scala similarity index 94% rename from src/main/scala/lc/FilterChallenge.scala rename to src/main/scala/lc/captchas/FilterChallenge.scala index e6a6692..11e029e 100644 --- a/src/main/scala/lc/FilterChallenge.scala +++ b/src/main/scala/lc/captchas/FilterChallenge.scala @@ -1,10 +1,13 @@ -package lc +package lc.captchas import com.sksamuel.scrimage._ import com.sksamuel.scrimage.filter._ import java.awt.image.BufferedImage import java.awt.Font import java.awt.Color +import lc.captchas.interfaces.ChallengeProvider +import lc.captchas.interfaces.Challenge + class FilterChallenge extends ChallengeProvider { def getId = "FilterChallenge" diff --git a/src/main/scala/lc/LabelCaptcha.scala b/src/main/scala/lc/captchas/LabelCaptcha.scala similarity index 96% rename from src/main/scala/lc/LabelCaptcha.scala rename to src/main/scala/lc/captchas/LabelCaptcha.scala index 9757051..de3c5cf 100644 --- a/src/main/scala/lc/LabelCaptcha.scala +++ b/src/main/scala/lc/captchas/LabelCaptcha.scala @@ -1,4 +1,4 @@ -package lc +package lc.captchas import java.io.File import java.io.ByteArrayOutputStream @@ -7,6 +7,8 @@ import scala.collection.mutable.Map import java.nio.file.{Files,Path,StandardCopyOption} import java.awt.image.BufferedImage import java.awt.{Graphics2D,Color} +import lc.captchas.interfaces.ChallengeProvider +import lc.captchas.interfaces.Challenge class LabelCaptcha extends ChallengeProvider { private var knownFiles = new File("known").list.toList diff --git a/src/main/scala/lc/RainDropsCaptcha.scala b/src/main/scala/lc/captchas/RainDropsCaptcha.scala similarity index 96% rename from src/main/scala/lc/RainDropsCaptcha.scala rename to src/main/scala/lc/captchas/RainDropsCaptcha.scala index 089f607..0d7c1f9 100644 --- a/src/main/scala/lc/RainDropsCaptcha.scala +++ b/src/main/scala/lc/captchas/RainDropsCaptcha.scala @@ -1,4 +1,4 @@ -package lc +package lc.captchas import java.awt.image.BufferedImage import java.awt.RenderingHints @@ -9,6 +9,9 @@ import java.io.ByteArrayOutputStream import javax.imageio.ImageIO import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; +import lc.captchas.interfaces.ChallengeProvider +import lc.captchas.interfaces.Challenge +import lc.misc.GifSequenceWriter class Drop { var x = 0 diff --git a/src/main/scala/lc/core/captcha.scala b/src/main/scala/lc/core/captcha.scala new file mode 100644 index 0000000..f7673de --- /dev/null +++ b/src/main/scala/lc/core/captcha.scala @@ -0,0 +1,121 @@ +package lc.core + +import org.json4s.JsonAST.JValue +import java.sql.{Blob, ResultSet} +import java.util.UUID +import java.io.ByteArrayInputStream +import lc.database.Statements +import lc.core.CaptchaProviders + +class Captcha { + + def getCaptcha(id: Id): Array[Byte] = { + var image :Array[Byte] = null + var blob: Blob = null + try { + val imagePstmt = Statements.tlStmts.get.imagePstmt + imagePstmt.setString(1, id.id) + val rs: ResultSet = imagePstmt.executeQuery() + if(rs.next()){ + blob = rs.getBlob("image") + if(blob != null){ + image = blob.getBytes(1, blob.length().toInt) + } + } + image + } catch { case e: Exception => + println(e) + image + } + } + + def generateChallenge(param: Parameters): Int = { + //TODO: eval params to choose a provider + val provider = CaptchaProviders.getProvider() + val providerId = provider.getId() + val challenge = provider.returnChallenge() + val blob = new ByteArrayInputStream(challenge.content) + val insertPstmt = Statements.tlStmts.get.insertPstmt + insertPstmt.setString(1, provider.getId) + insertPstmt.setString(2, challenge.secret) + insertPstmt.setString(3, providerId) + insertPstmt.setString(4, challenge.contentType) + insertPstmt.setBlob(5, blob) + insertPstmt.executeUpdate() + val rs: ResultSet = insertPstmt.getGeneratedKeys() + val token = if(rs.next()){ + rs.getInt("token") + } + println("Added new challenge: "+ token.toString) + token.asInstanceOf[Int] + } + + def getChallenge(param: Parameters): Id = { + try { + val tokenPstmt = Statements.tlStmts.get.tokenPstmt + val rs = tokenPstmt.executeQuery() + val tokenOpt = if(rs.next()) { + Some(rs.getInt("token")) + } else { + None + } + val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt + val uuid = getUUID(tokenOpt.getOrElse(generateChallenge(param))) + updateAttemptedPstmt.setString(1, uuid) + updateAttemptedPstmt.executeUpdate() + Id(uuid) + } catch {case e: Exception => + println(e) + Id(getUUID(-1)) + } + } + + private def getUUID(id: Int): String = { + val uuid = UUID.randomUUID().toString + val mapPstmt = Statements.tlStmts.get.mapPstmt + mapPstmt.setString(1,uuid) + mapPstmt.setInt(2,id) + mapPstmt.executeUpdate() + uuid + } + + def checkAnswer(answer: Answer): Result = { + val selectPstmt = Statements.tlStmts.get.selectPstmt + selectPstmt.setString(1, answer.id) + val rs: ResultSet = selectPstmt.executeQuery() + val psOpt = 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) "TRUE" else "FALSE" + result + } else { + "EXPIRED" + } + val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt + deleteAnswerPstmt.setString(1, answer.id) + deleteAnswerPstmt.executeUpdate() + Result(psOpt) + } + + def display(): Unit = { + val rs: ResultSet = Statements.tlStmts.get.getChallengeTable.executeQuery() + println("token\t\tid\t\tsecret\t\tattempted") + while(rs.next()) { + val token = rs.getInt("token") + val id = rs.getString("id") + val secret = rs.getString("secret") + val attempted = rs.getString("attempted") + println(s"${token}\t\t${id}\t\t${secret}\t\t${attempted}\n\n") + } + + val rss: ResultSet = Statements.tlStmts.get.getMapIdTable.executeQuery() + println("uuid\t\ttoken\t\tlastServed") + while(rss.next()){ + val uuid = rss.getString("uuid") + val token = rss.getInt("token") + val lastServed = rss.getTimestamp("lastServed") + println(s"${uuid}\t\t${token}\t\t${lastServed}\n\n") + } + } +} \ No newline at end of file diff --git a/src/main/scala/lc/core/captchaProviders.scala b/src/main/scala/lc/core/captchaProviders.scala new file mode 100644 index 0000000..b7b315c --- /dev/null +++ b/src/main/scala/lc/core/captchaProviders.scala @@ -0,0 +1,38 @@ +package lc.core + +import lc.captchas._ +import lc.captchas.interfaces.ChallengeProvider + +object CaptchaProviders { + private val providers = Map( + "FilterChallenge" -> new FilterChallenge, + //"FontFunCaptcha" -> new FontFunCaptcha, + "GifCaptcha" -> new GifCaptcha, + "ShadowTextCaptcha" -> new ShadowTextCaptcha, + "RainDropsCaptcha" -> new RainDropsCP, + //"LabelCaptcha" -> new LabelCaptcha + ) + + def generateChallengeSamples() = { + providers.map {case (key, provider) => + (key, provider.returnChallenge()) + } + } + + private val seed = System.currentTimeMillis.toString.substring(2,6).toInt + private val random = new scala.util.Random(seed) + + private def getNextRandomInt(max: Int) = random.synchronized { + random.nextInt(max) + } + + def getProviderById(id: String): ChallengeProvider = { + return providers(id) + } + + def getProvider(): ChallengeProvider = { + val keys = providers.keys + val providerIndex = keys.toVector(getNextRandomInt(keys.size)) + providers(providerIndex) + } +} \ No newline at end of file diff --git a/src/main/scala/lc/core/models.scala b/src/main/scala/lc/core/models.scala new file mode 100644 index 0000000..cceb90f --- /dev/null +++ b/src/main/scala/lc/core/models.scala @@ -0,0 +1,7 @@ +package lc.core + +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) +case class Answer(answer: String, id: String) +case class Result(result: String) \ No newline at end of file diff --git a/src/main/scala/lc/DB.scala b/src/main/scala/lc/database/DB.scala similarity index 92% rename from src/main/scala/lc/DB.scala rename to src/main/scala/lc/database/DB.scala index cf1641b..c2014b4 100644 --- a/src/main/scala/lc/DB.scala +++ b/src/main/scala/lc/database/DB.scala @@ -1,4 +1,4 @@ -package lc +package lc.database import java.sql._ diff --git a/src/main/scala/lc/database/statements.scala b/src/main/scala/lc/database/statements.scala new file mode 100644 index 0000000..393c79a --- /dev/null +++ b/src/main/scala/lc/database/statements.scala @@ -0,0 +1,31 @@ +package lc.database + +import lc.database.DBConn +import java.sql.Statement + +class Statements(dbConn: DBConn) { + + private val stmt = dbConn.getStatement() + + stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token int auto_increment, id varchar, secret varchar, provider varchar, contentType varchar, image blob, attempted int default 0, PRIMARY KEY(token))") + stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token int, lastServed timestamp, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token) ON DELETE CASCADE)") + + val insertPstmt = dbConn.con.prepareStatement("INSERT INTO challenge(id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS ) + val mapPstmt = dbConn.con.prepareStatement("INSERT INTO mapId(uuid, token, lastServed) VALUES (?, ?, CURRENT_TIMESTAMP)") + val selectPstmt = dbConn.con.prepareStatement("SELECT c.secret, c.provider FROM challenge c, mapId m WHERE m.token=c.token AND DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, m.lastServed)) > 0 AND m.uuid = ?") + val imagePstmt = dbConn.con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") + val updateAttemptedPstmt = dbConn.con.prepareStatement("UPDATE challenge SET attempted = attempted+1 WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ?)") + val tokenPstmt = dbConn.con.prepareStatement("SELECT token FROM challenge WHERE attempted < 10 ORDER BY RAND() LIMIT 1") + val deleteAnswerPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE uuid = ?") + val challengeGCPstmt = dbConn.con.prepareStatement("DELETE FROM challenge WHERE attempted >= 10 AND token NOT IN (SELECT token FROM mapId)") + val mapIdGCPstmt = dbConn.con.prepareStatement("DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0") + + val getCountChallengeTable = dbConn.con.prepareStatement("SELECT COUNT(*) AS total FROM challenge") + val getChallengeTable = dbConn.con.prepareStatement("SELECT * FROM challenge") + val getMapIdTable = dbConn.con.prepareStatement("SELECT * FROM mapId") +} + +object Statements { + private val dbConn: DBConn = new DBConn() + val tlStmts = ThreadLocal.withInitial(() => new Statements(dbConn)) +} \ No newline at end of file diff --git a/src/main/scala/lc/Server.scala b/src/main/scala/lc/server/Server.scala similarity index 63% rename from src/main/scala/lc/Server.scala rename to src/main/scala/lc/server/Server.scala index d8ccb7f..0bb19c8 100644 --- a/src/main/scala/lc/Server.scala +++ b/src/main/scala/lc/server/Server.scala @@ -1,27 +1,26 @@ -package lc +package lc.server -import java.io.File -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.jackson.Serialization.{read, write} +import org.json4s.DefaultFormats +import org.json4s.jackson.JsonMethods.parse +import org.json4s.jackson.Serialization.write +import lc.core.Captcha +import lc.core.{Parameters, Id, Answer} +import lc.server.HTTPServer -import lc.HTTPServer._ -case class Secret(token: Int) - -class Server(port: Int, captcha: Captcha, dbConn: DBConn){ +class Server(port: Int, captcha: Captcha){ val server = new HTTPServer(port) val host = server.getVirtualHost(null) implicit val formats = DefaultFormats host.addContext("/v1/captcha",(req, resp) => { - val body = req.getJson() + val body = req.getJson() val json = parse(body) val param = json.extract[Parameters] - val id = captcha.getChallenge(param) - resp.getHeaders().add("Content-Type","application/json") - resp.send(200, write(id)) + val id = captcha.getChallenge(param) + resp.getHeaders().add("Content-Type","application/json") + resp.send(200, write(id)) 0 },"POST") @@ -40,8 +39,7 @@ class Server(port: Int, captcha: Captcha, dbConn: DBConn){ val answer = json.extract[Answer] val result = captcha.checkAnswer(answer) resp.getHeaders().add("Content-Type","application/json") - val responseContent = s"""{"result":"$result"}""" - resp.send(200,responseContent) + resp.send(200, write(result)) 0 },"POST")