Refactor modules (#56)
* Refactor:Modules Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com> * Add config file Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com> * Restore sample images Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>
This commit is contained in:
parent
b66f777828
commit
6d04cdc3b4
|
@ -5,6 +5,9 @@
|
||||||
/project/**
|
/project/**
|
||||||
/target/
|
/target/
|
||||||
**__pycache__
|
**__pycache__
|
||||||
|
.bloop
|
||||||
|
.metals
|
||||||
|
.vscode
|
||||||
|
|
||||||
# for various captcha
|
# for various captcha
|
||||||
/known/
|
/known/
|
||||||
|
|
|
@ -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":{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package lc;
|
package lc.captchas;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
@ -6,6 +6,9 @@ import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import lc.captchas.interfaces.Challenge;
|
||||||
|
import lc.captchas.interfaces.ChallengeProvider;
|
||||||
|
import lc.misc.HelperFunctions;
|
||||||
|
|
||||||
public class FontFunCaptcha implements ChallengeProvider{
|
public class FontFunCaptcha implements ChallengeProvider{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package lc;
|
package lc.captchas;
|
||||||
|
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
|
@ -9,6 +9,10 @@ import java.io.IOException;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
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{
|
public class GifCaptcha implements ChallengeProvider{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package lc;
|
package lc.captchas;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
|
@ -10,11 +10,14 @@ import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.ConvolveOp;
|
import java.awt.image.ConvolveOp;
|
||||||
import java.awt.image.Kernel;
|
import java.awt.image.Kernel;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import lc.misc.HelperFunctions;
|
||||||
|
import lc.captchas.interfaces.Challenge;
|
||||||
|
import lc.captchas.interfaces.ChallengeProvider;
|
||||||
|
|
||||||
public class ShadowTextCaptcha implements ChallengeProvider{
|
public class ShadowTextCaptcha implements ChallengeProvider{
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "ShadowText";
|
return "ShadowTextCaptcha";
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkAnswer(String secret, String answer) {
|
public boolean checkAnswer(String secret, String answer) {
|
|
@ -1,4 +1,4 @@
|
||||||
package lc;
|
package lc.captchas.interfaces;
|
||||||
|
|
||||||
public class Challenge {
|
public class Challenge {
|
||||||
public final byte[] content;
|
public final byte[] content;
|
|
@ -1,6 +1,6 @@
|
||||||
package lc;
|
package lc.captchas.interfaces;
|
||||||
|
|
||||||
interface ChallengeProvider {
|
public interface ChallengeProvider {
|
||||||
public String getId();
|
public String getId();
|
||||||
public Challenge returnChallenge();
|
public Challenge returnChallenge();
|
||||||
public boolean checkAnswer(String secret, String answer);
|
public boolean checkAnswer(String secret, String answer);
|
|
@ -1,7 +1,7 @@
|
||||||
// This code was adapted from http://elliot.kroo.net/software/java/GifSequenceWriter/
|
// This code was adapted from http://elliot.kroo.net/software/java/GifSequenceWriter/
|
||||||
// It was available under CC By 3.0
|
// It was available under CC By 3.0
|
||||||
|
|
||||||
package lc;
|
package lc.misc;
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
import javax.imageio.metadata.*;
|
import javax.imageio.metadata.*;
|
||||||
import javax.imageio.stream.*;
|
import javax.imageio.stream.*;
|
|
@ -1,4 +1,4 @@
|
||||||
package lc;
|
package lc.misc;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package lc;/*
|
package lc.server;/*
|
||||||
* Copyright © 2005-2018 Amichai Rothman
|
* Copyright © 2005-2018 Amichai Rothman
|
||||||
*
|
*
|
||||||
* This file is part of JLHTTP - the Java Lightweight HTTP Server.
|
* This file is part of JLHTTP - the Java Lightweight HTTP Server.
|
|
@ -1,222 +1,20 @@
|
||||||
package lc
|
package lc
|
||||||
|
|
||||||
import com.sksamuel.scrimage._
|
import org.json4s.DefaultFormats
|
||||||
import java.io.ByteArrayInputStream
|
import org.json4s.jackson.JsonMethods._
|
||||||
import java.util.concurrent._
|
import scala.io.Source.fromFile
|
||||||
import java.util.UUID
|
import lc.database.Statements
|
||||||
import java.sql.{Blob, ResultSet}
|
import lc.core.{Captcha, CaptchaProviders}
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import lc.server.Server
|
||||||
import java.io._
|
import lc.background.BackgroundTask
|
||||||
import java.sql.Statement
|
|
||||||
|
|
||||||
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{
|
object LCFramework{
|
||||||
def main(args: scala.Array[String]) {
|
def main(args: scala.Array[String]) {
|
||||||
val dbConn = new DBConn()
|
val captcha = new Captcha()
|
||||||
Statements.dbConn = dbConn
|
val server = new Server(8888, captcha)
|
||||||
val captcha = new Captcha(2, dbConn)
|
val backgroudTask = new BackgroundTask(captcha, 10)
|
||||||
val server = new Server(8888, captcha, dbConn)
|
backgroudTask.beginThread(2)
|
||||||
captcha.beginThread(2)
|
|
||||||
server.start()
|
server.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
package lc
|
package lc.captchas
|
||||||
|
|
||||||
import com.sksamuel.scrimage._
|
import com.sksamuel.scrimage._
|
||||||
import com.sksamuel.scrimage.filter._
|
import com.sksamuel.scrimage.filter._
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
|
import lc.captchas.interfaces.Challenge
|
||||||
|
|
||||||
|
|
||||||
class FilterChallenge extends ChallengeProvider {
|
class FilterChallenge extends ChallengeProvider {
|
||||||
def getId = "FilterChallenge"
|
def getId = "FilterChallenge"
|
|
@ -1,4 +1,4 @@
|
||||||
package lc
|
package lc.captchas
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
@ -7,6 +7,8 @@ import scala.collection.mutable.Map
|
||||||
import java.nio.file.{Files,Path,StandardCopyOption}
|
import java.nio.file.{Files,Path,StandardCopyOption}
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.awt.{Graphics2D,Color}
|
import java.awt.{Graphics2D,Color}
|
||||||
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
|
import lc.captchas.interfaces.Challenge
|
||||||
|
|
||||||
class LabelCaptcha extends ChallengeProvider {
|
class LabelCaptcha extends ChallengeProvider {
|
||||||
private var knownFiles = new File("known").list.toList
|
private var knownFiles = new File("known").list.toList
|
|
@ -1,4 +1,4 @@
|
||||||
package lc
|
package lc.captchas
|
||||||
|
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.awt.RenderingHints
|
import java.awt.RenderingHints
|
||||||
|
@ -9,6 +9,9 @@ import java.io.ByteArrayOutputStream
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||||
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
|
import lc.captchas.interfaces.Challenge
|
||||||
|
import lc.misc.GifSequenceWriter
|
||||||
|
|
||||||
class Drop {
|
class Drop {
|
||||||
var x = 0
|
var x = 0
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -1,4 +1,4 @@
|
||||||
package lc
|
package lc.database
|
||||||
|
|
||||||
import java.sql._
|
import java.sql._
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
|
@ -1,27 +1,26 @@
|
||||||
package lc
|
package lc.server
|
||||||
|
|
||||||
import java.io.File
|
import org.json4s.DefaultFormats
|
||||||
import org.json4s._
|
import org.json4s.jackson.JsonMethods.parse
|
||||||
import org.json4s.jackson.JsonMethods._
|
import org.json4s.jackson.Serialization.write
|
||||||
import org.json4s.jackson.Serialization.{read, 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){
|
||||||
|
|
||||||
class Server(port: Int, captcha: Captcha, dbConn: DBConn){
|
|
||||||
val server = new HTTPServer(port)
|
val server = new HTTPServer(port)
|
||||||
val host = server.getVirtualHost(null)
|
val host = server.getVirtualHost(null)
|
||||||
|
|
||||||
implicit val formats = DefaultFormats
|
implicit val formats = DefaultFormats
|
||||||
|
|
||||||
host.addContext("/v1/captcha",(req, resp) => {
|
host.addContext("/v1/captcha",(req, resp) => {
|
||||||
val body = req.getJson()
|
val body = req.getJson()
|
||||||
val json = parse(body)
|
val json = parse(body)
|
||||||
val param = json.extract[Parameters]
|
val param = json.extract[Parameters]
|
||||||
val id = captcha.getChallenge(param)
|
val id = captcha.getChallenge(param)
|
||||||
resp.getHeaders().add("Content-Type","application/json")
|
resp.getHeaders().add("Content-Type","application/json")
|
||||||
resp.send(200, write(id))
|
resp.send(200, write(id))
|
||||||
0
|
0
|
||||||
},"POST")
|
},"POST")
|
||||||
|
|
||||||
|
@ -40,8 +39,7 @@ class Server(port: Int, captcha: Captcha, dbConn: DBConn){
|
||||||
val answer = json.extract[Answer]
|
val answer = json.extract[Answer]
|
||||||
val result = captcha.checkAnswer(answer)
|
val result = captcha.checkAnswer(answer)
|
||||||
resp.getHeaders().add("Content-Type","application/json")
|
resp.getHeaders().add("Content-Type","application/json")
|
||||||
val responseContent = s"""{"result":"$result"}"""
|
resp.send(200, write(result))
|
||||||
resp.send(200,responseContent)
|
|
||||||
0
|
0
|
||||||
},"POST")
|
},"POST")
|
||||||
|
|
Loading…
Reference in New Issue