mirror of
https://github.com/librecaptcha/lc-core.git
synced 2025-01-13 06:53:19 -05:00
commit
055d999e17
4
.github/workflows/scala.yml
vendored
4
.github/workflows/scala.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK 1.11
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 1.11
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: sbt test
|
run: sbt test
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
|
@ -21,7 +21,6 @@ scalacOptions ++= List(
|
|||||||
"-Ywarn-unused"
|
"-Ywarn-unused"
|
||||||
)
|
)
|
||||||
javacOptions += "-g:none"
|
javacOptions += "-g:none"
|
||||||
scalafmtOnCompile := true
|
|
||||||
compileOrder := CompileOrder.JavaThenScala
|
compileOrder := CompileOrder.JavaThenScala
|
||||||
|
|
||||||
fork in run := true
|
fork in run := true
|
||||||
|
39
config.json
39
config.json
@ -1,34 +1,37 @@
|
|||||||
{
|
{
|
||||||
"randomSeed": 20,
|
"randomSeed": 20,
|
||||||
|
"port": 8888,
|
||||||
"captchaExpiryTimeLimit": 5,
|
"captchaExpiryTimeLimit": 5,
|
||||||
"captchas":{
|
"threadDelay": 2,
|
||||||
"FilterChallenge":{
|
"throttle": 10,
|
||||||
|
"captchas":[
|
||||||
|
{
|
||||||
"name": "FilterChallenge",
|
"name": "FilterChallenge",
|
||||||
"supportedLevels":["medium", "hard"],
|
"allowedLevels":["medium", "hard"],
|
||||||
"supportedMedia":["image"],
|
"allowedMedia":["image/png"],
|
||||||
"supportedinputType":["text"],
|
"allowedInputType":["text"],
|
||||||
"config":{}
|
"config":{}
|
||||||
},
|
},
|
||||||
"GifCaptcha":{
|
{
|
||||||
"name": "GifCaptcha",
|
"name": "GifCaptcha",
|
||||||
"supportedLevels":["hard"],
|
"allowedLevels":["hard"],
|
||||||
"supportedMedia":["gif"],
|
"allowedMedia":["image/gif"],
|
||||||
"supportedinputType":["text"],
|
"allowedInputType":["text"],
|
||||||
"config":{}
|
"config":{}
|
||||||
},
|
},
|
||||||
"ShadowTextCaptcha":{
|
{
|
||||||
"name": "ShadowTextCaptcha",
|
"name": "ShadowTextCaptcha",
|
||||||
"supportedLevels":["easy"],
|
"allowedLevels":["easy"],
|
||||||
"supportedMedia":["image"],
|
"allowedMedia":["image/png"],
|
||||||
"supportedinputType":["text"],
|
"allowedInputType":["text"],
|
||||||
"config": {}
|
"config": {}
|
||||||
},
|
},
|
||||||
"RainDropsCaptcha":{
|
{
|
||||||
"name": "RainDropsCaptcha",
|
"name": "RainDropsCaptcha",
|
||||||
"supportedLevels":["easy","medium"],
|
"allowedLevels":["easy","medium"],
|
||||||
"supportedMedia":["image"],
|
"allowedMedia":["image/gif"],
|
||||||
"supportedinputType":["text"],
|
"allowedInputType":["text"],
|
||||||
"config":{}
|
"config":{}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
@ -6,6 +6,8 @@ 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 java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import lc.captchas.interfaces.Challenge;
|
import lc.captchas.interfaces.Challenge;
|
||||||
import lc.captchas.interfaces.ChallengeProvider;
|
import lc.captchas.interfaces.ChallengeProvider;
|
||||||
import lc.misc.HelperFunctions;
|
import lc.misc.HelperFunctions;
|
||||||
@ -16,6 +18,19 @@ public class FontFunCaptcha implements ChallengeProvider {
|
|||||||
return "FontFunCaptcha";
|
return "FontFunCaptcha";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap<String, List<String>> supportedParameters() {
|
||||||
|
HashMap<String, List<String>> supportedParams = new HashMap<String, List<String>>();
|
||||||
|
supportedParams.put("supportedLevels", List.of("medium"));
|
||||||
|
supportedParams.put("supportedMedia", List.of("image/png"));
|
||||||
|
supportedParams.put("supportedInputType", List.of("text"));
|
||||||
|
|
||||||
|
return supportedParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configure(String config) {
|
||||||
|
// TODO: Add custom config
|
||||||
|
}
|
||||||
|
|
||||||
private String getFontName(String path, String level) {
|
private String getFontName(String path, String level) {
|
||||||
File file = new File(path + level + "/");
|
File file = new File(path + level + "/");
|
||||||
FilenameFilter txtFileFilter =
|
FilenameFilter txtFileFilter =
|
||||||
|
@ -6,6 +6,9 @@ import java.awt.RenderingHints;
|
|||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
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;
|
||||||
@ -48,6 +51,19 @@ public class GifCaptcha implements ChallengeProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void configure(String config) {
|
||||||
|
// TODO: Add custom config
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, List<String>> supportedParameters() {
|
||||||
|
HashMap<String, List<String>> supportedParams = new HashMap<String, List<String>>();
|
||||||
|
supportedParams.put("supportedLevels", List.of("hard"));
|
||||||
|
supportedParams.put("supportedMedia", List.of("image/gif"));
|
||||||
|
supportedParams.put("supportedInputType", List.of("text"));
|
||||||
|
|
||||||
|
return supportedParams;
|
||||||
|
}
|
||||||
|
|
||||||
public Challenge returnChallenge() {
|
public Challenge returnChallenge() {
|
||||||
String secret = HelperFunctions.randomString(6);
|
String secret = HelperFunctions.randomString(6);
|
||||||
return new Challenge(gifCaptcha(secret), "image/gif", secret.toLowerCase());
|
return new Challenge(gifCaptcha(secret), "image/gif", secret.toLowerCase());
|
||||||
|
@ -10,6 +10,9 @@ 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 java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import lc.misc.HelperFunctions;
|
import lc.misc.HelperFunctions;
|
||||||
import lc.captchas.interfaces.Challenge;
|
import lc.captchas.interfaces.Challenge;
|
||||||
import lc.captchas.interfaces.ChallengeProvider;
|
import lc.captchas.interfaces.ChallengeProvider;
|
||||||
@ -20,6 +23,19 @@ public class ShadowTextCaptcha implements ChallengeProvider {
|
|||||||
return "ShadowTextCaptcha";
|
return "ShadowTextCaptcha";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void configure(String config) {
|
||||||
|
// TODO: Add custom config
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, List<String>> supportedParameters() {
|
||||||
|
HashMap<String, List<String>> supportedParams = new HashMap<String, List<String>>();
|
||||||
|
supportedParams.put("supportedLevels", List.of("easy"));
|
||||||
|
supportedParams.put("supportedMedia", List.of("image/png"));
|
||||||
|
supportedParams.put("supportedInputType", List.of("text"));
|
||||||
|
|
||||||
|
return supportedParams;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean checkAnswer(String secret, String answer) {
|
public boolean checkAnswer(String secret, String answer) {
|
||||||
return answer.toLowerCase().equals(secret);
|
return answer.toLowerCase().equals(secret);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package lc.captchas.interfaces;
|
package lc.captchas.interfaces;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ChallengeProvider {
|
public interface ChallengeProvider {
|
||||||
public String getId();
|
public String getId();
|
||||||
|
|
||||||
@ -7,5 +10,7 @@ public interface ChallengeProvider {
|
|||||||
|
|
||||||
public boolean checkAnswer(String secret, String answer);
|
public boolean checkAnswer(String secret, String answer);
|
||||||
|
|
||||||
// TODO: def configure(): Unit
|
public void configure(String config);
|
||||||
|
|
||||||
|
public Map<String, List<String>> supportedParameters();
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,18 @@ package lc
|
|||||||
import lc.core.{Captcha, CaptchaProviders}
|
import lc.core.{Captcha, CaptchaProviders}
|
||||||
import lc.server.Server
|
import lc.server.Server
|
||||||
import lc.background.BackgroundTask
|
import lc.background.BackgroundTask
|
||||||
|
import lc.core.Config
|
||||||
|
|
||||||
object LCFramework {
|
object LCFramework {
|
||||||
def main(args: scala.Array[String]): Unit = {
|
def main(args: scala.Array[String]): Unit = {
|
||||||
val captcha = new Captcha()
|
val captcha = new Captcha()
|
||||||
val server = new Server(8888, captcha)
|
val server = new Server(port = Config.port, captcha = captcha)
|
||||||
val backgroudTask = new BackgroundTask(captcha, 10)
|
val backgroundTask = new BackgroundTask(
|
||||||
backgroudTask.beginThread(2)
|
captcha = captcha,
|
||||||
|
throttle = Config.throttle,
|
||||||
|
timeLimit = Config.captchaExpiryTimeLimit
|
||||||
|
)
|
||||||
|
backgroundTask.beginThread(delay = Config.threadDelay)
|
||||||
server.start()
|
server.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,14 @@ import java.util.concurrent.{ScheduledThreadPoolExecutor, TimeUnit}
|
|||||||
import lc.core.Captcha
|
import lc.core.Captcha
|
||||||
import lc.core.{Parameters, Size}
|
import lc.core.{Parameters, Size}
|
||||||
|
|
||||||
class BackgroundTask(captcha: Captcha, throttle: Int) {
|
class BackgroundTask(captcha: Captcha, throttle: Int, timeLimit: Int) {
|
||||||
|
|
||||||
private val task = new Runnable {
|
private val task = new Runnable {
|
||||||
def run(): Unit = {
|
def run(): Unit = {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
val mapIdGCPstmt = Statements.tlStmts.get.mapIdGCPstmt
|
val mapIdGCPstmt = Statements.tlStmts.get.mapIdGCPstmt
|
||||||
|
mapIdGCPstmt.setInt(1, timeLimit)
|
||||||
mapIdGCPstmt.executeUpdate()
|
mapIdGCPstmt.executeUpdate()
|
||||||
|
|
||||||
val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt
|
val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt
|
||||||
@ -22,7 +23,7 @@ class BackgroundTask(captcha: Captcha, throttle: Int) {
|
|||||||
if (imageNum.next())
|
if (imageNum.next())
|
||||||
throttleIn = (throttleIn - imageNum.getInt("total"))
|
throttleIn = (throttleIn - imageNum.getInt("total"))
|
||||||
while (0 < throttleIn) {
|
while (0 < throttleIn) {
|
||||||
captcha.generateChallenge(Parameters("", "", "", Option(Size(0, 0))))
|
captcha.generateChallenge(Parameters("medium", "image/png", "text", Option(Size(0, 0))))
|
||||||
throttleIn -= 1
|
throttleIn -= 1
|
||||||
}
|
}
|
||||||
} catch { case e: Exception => println(e) }
|
} catch { case e: Exception => println(e) }
|
||||||
|
@ -7,9 +7,26 @@ import java.awt.Font
|
|||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import lc.captchas.interfaces.ChallengeProvider
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
import lc.captchas.interfaces.Challenge
|
import lc.captchas.interfaces.Challenge
|
||||||
|
import scala.jdk.CollectionConverters.MapHasAsJava
|
||||||
|
import java.util.{List => JavaList, Map => JavaMap}
|
||||||
|
|
||||||
class FilterChallenge extends ChallengeProvider {
|
class FilterChallenge extends ChallengeProvider {
|
||||||
def getId = "FilterChallenge"
|
def getId = "FilterChallenge"
|
||||||
|
|
||||||
|
def configure(config: String): Unit = {
|
||||||
|
// TODO: add custom config
|
||||||
|
}
|
||||||
|
|
||||||
|
def supportedParameters(): JavaMap[String, JavaList[String]] = {
|
||||||
|
val supportedParams = Map(
|
||||||
|
"supportedLevels" -> JavaList.of("medium", "hard"),
|
||||||
|
"supportedMedia" -> JavaList.of("image/png"),
|
||||||
|
"supportedInputType" -> JavaList.of("text")
|
||||||
|
).asJava
|
||||||
|
|
||||||
|
supportedParams
|
||||||
|
}
|
||||||
|
|
||||||
def returnChallenge(): Challenge = {
|
def returnChallenge(): Challenge = {
|
||||||
val filterTypes = List(new FilterType1, new FilterType2)
|
val filterTypes = List(new FilterType1, new FilterType2)
|
||||||
val r = new scala.util.Random
|
val r = new scala.util.Random
|
||||||
|
@ -9,6 +9,8 @@ import java.awt.image.BufferedImage
|
|||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import lc.captchas.interfaces.ChallengeProvider
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
import lc.captchas.interfaces.Challenge
|
import lc.captchas.interfaces.Challenge
|
||||||
|
import scala.jdk.CollectionConverters.MapHasAsJava
|
||||||
|
import java.util.{List => JavaList, Map => JavaMap}
|
||||||
|
|
||||||
class LabelCaptcha extends ChallengeProvider {
|
class LabelCaptcha extends ChallengeProvider {
|
||||||
private var knownFiles = new File("known").list.toList
|
private var knownFiles = new File("known").list.toList
|
||||||
@ -23,6 +25,20 @@ class LabelCaptcha extends ChallengeProvider {
|
|||||||
|
|
||||||
def getId = "LabelCaptcha"
|
def getId = "LabelCaptcha"
|
||||||
|
|
||||||
|
def configure(config: String): Unit = {
|
||||||
|
// TODO: add custom config
|
||||||
|
}
|
||||||
|
|
||||||
|
def supportedParameters(): JavaMap[String, JavaList[String]] = {
|
||||||
|
val supportedParams = Map(
|
||||||
|
"supportedLevels" -> JavaList.of("hard"),
|
||||||
|
"supportedMedia" -> JavaList.of("image/png"),
|
||||||
|
"supportedInputType" -> JavaList.of("text")
|
||||||
|
).asJava
|
||||||
|
|
||||||
|
supportedParams
|
||||||
|
}
|
||||||
|
|
||||||
def returnChallenge(): Challenge =
|
def returnChallenge(): Challenge =
|
||||||
synchronized {
|
synchronized {
|
||||||
val r = scala.util.Random.nextInt(knownFiles.length)
|
val r = scala.util.Random.nextInt(knownFiles.length)
|
||||||
|
@ -10,6 +10,8 @@ import javax.imageio.stream.MemoryCacheImageOutputStream;
|
|||||||
import lc.captchas.interfaces.ChallengeProvider
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
import lc.captchas.interfaces.Challenge
|
import lc.captchas.interfaces.Challenge
|
||||||
import lc.misc.GifSequenceWriter
|
import lc.misc.GifSequenceWriter
|
||||||
|
import scala.jdk.CollectionConverters.MapHasAsJava
|
||||||
|
import java.util.{List => JavaList, Map => JavaMap}
|
||||||
|
|
||||||
class Drop {
|
class Drop {
|
||||||
var x = 0
|
var x = 0
|
||||||
@ -31,6 +33,20 @@ class RainDropsCP extends ChallengeProvider {
|
|||||||
|
|
||||||
def getId = "FilterChallenge"
|
def getId = "FilterChallenge"
|
||||||
|
|
||||||
|
def configure(config: String): Unit = {
|
||||||
|
// TODO: add custom config
|
||||||
|
}
|
||||||
|
|
||||||
|
def supportedParameters(): JavaMap[String, JavaList[String]] = {
|
||||||
|
val supportedParams = Map(
|
||||||
|
"supportedLevels" -> JavaList.of("medium", "easy"),
|
||||||
|
"supportedMedia" -> JavaList.of("image/gif"),
|
||||||
|
"supportedInputType" -> JavaList.of("text")
|
||||||
|
).asJava
|
||||||
|
|
||||||
|
supportedParams
|
||||||
|
}
|
||||||
|
|
||||||
private def extendDrops(drops: Array[Drop], steps: Int, xOffset: Int) = {
|
private def extendDrops(drops: Array[Drop], steps: Int, xOffset: Int) = {
|
||||||
drops.map(d => {
|
drops.map(d => {
|
||||||
val nd = new Drop()
|
val nd = new Drop()
|
||||||
|
@ -5,6 +5,7 @@ import java.util.UUID
|
|||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import lc.database.Statements
|
import lc.database.Statements
|
||||||
import lc.core.CaptchaProviders
|
import lc.core.CaptchaProviders
|
||||||
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
|
|
||||||
class Captcha {
|
class Captcha {
|
||||||
|
|
||||||
@ -30,8 +31,8 @@ class Captcha {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generateChallenge(param: Parameters): Int = {
|
def generateChallenge(param: Parameters): Int = {
|
||||||
//TODO: eval params to choose a provider
|
val provider = CaptchaProviders.getProvider(param)
|
||||||
val provider = CaptchaProviders.getProvider()
|
if (!provider.isInstanceOf[ChallengeProvider]) return -1
|
||||||
val providerId = provider.getId()
|
val providerId = provider.getId()
|
||||||
val challenge = provider.returnChallenge()
|
val challenge = provider.returnChallenge()
|
||||||
val blob = new ByteArrayInputStream(challenge.content)
|
val blob = new ByteArrayInputStream(challenge.content)
|
||||||
@ -40,7 +41,9 @@ class Captcha {
|
|||||||
insertPstmt.setString(2, challenge.secret)
|
insertPstmt.setString(2, challenge.secret)
|
||||||
insertPstmt.setString(3, providerId)
|
insertPstmt.setString(3, providerId)
|
||||||
insertPstmt.setString(4, challenge.contentType)
|
insertPstmt.setString(4, challenge.contentType)
|
||||||
insertPstmt.setBlob(5, blob)
|
insertPstmt.setString(5, param.level)
|
||||||
|
insertPstmt.setString(6, param.input_type)
|
||||||
|
insertPstmt.setBlob(7, blob)
|
||||||
insertPstmt.executeUpdate()
|
insertPstmt.executeUpdate()
|
||||||
val rs: ResultSet = insertPstmt.getGeneratedKeys()
|
val rs: ResultSet = insertPstmt.getGeneratedKeys()
|
||||||
val token = if (rs.next()) {
|
val token = if (rs.next()) {
|
||||||
@ -50,24 +53,53 @@ class Captcha {
|
|||||||
token.asInstanceOf[Int]
|
token.asInstanceOf[Int]
|
||||||
}
|
}
|
||||||
|
|
||||||
def getChallenge(param: Parameters): Id = {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
def getChallenge(param: Parameters): ChallengeResult = {
|
||||||
try {
|
try {
|
||||||
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
|
val validParam = validateParam(param)
|
||||||
val rs = tokenPstmt.executeQuery()
|
if (validParam) {
|
||||||
val tokenOpt = if (rs.next()) {
|
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
|
||||||
Some(rs.getInt("token"))
|
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
|
||||||
|
}
|
||||||
|
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 {
|
} else {
|
||||||
None
|
Error(ErrorMessageEnum.INVALID_PARAM.toString)
|
||||||
}
|
}
|
||||||
val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt
|
|
||||||
val uuid = getUUID(tokenOpt.getOrElse(generateChallenge(param)))
|
|
||||||
updateAttemptedPstmt.setString(1, uuid)
|
|
||||||
updateAttemptedPstmt.executeUpdate()
|
|
||||||
Id(uuid)
|
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception =>
|
case e: Exception =>
|
||||||
println(e)
|
println(e)
|
||||||
Id(getUUID(-1))
|
Error(ErrorMessageEnum.SMW.toString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,16 +114,17 @@ class Captcha {
|
|||||||
|
|
||||||
def checkAnswer(answer: Answer): Result = {
|
def checkAnswer(answer: Answer): Result = {
|
||||||
val selectPstmt = Statements.tlStmts.get.selectPstmt
|
val selectPstmt = Statements.tlStmts.get.selectPstmt
|
||||||
selectPstmt.setString(1, answer.id)
|
selectPstmt.setInt(1, Config.captchaExpiryTimeLimit)
|
||||||
|
selectPstmt.setString(2, answer.id)
|
||||||
val rs: ResultSet = selectPstmt.executeQuery()
|
val rs: ResultSet = selectPstmt.executeQuery()
|
||||||
val psOpt = if (rs.first()) {
|
val psOpt = if (rs.first()) {
|
||||||
val secret = rs.getString("secret")
|
val secret = rs.getString("secret")
|
||||||
val provider = rs.getString("provider")
|
val provider = rs.getString("provider")
|
||||||
val check = CaptchaProviders.getProviderById(provider).checkAnswer(secret, answer.answer)
|
val check = CaptchaProviders.getProviderById(provider).checkAnswer(secret, answer.answer)
|
||||||
val result = if (check) "TRUE" else "FALSE"
|
val result = if (check) ResultEnum.TRUE.toString else ResultEnum.FALSE.toString
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
"EXPIRED"
|
ResultEnum.EXPIRED.toString
|
||||||
}
|
}
|
||||||
val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt
|
val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt
|
||||||
deleteAnswerPstmt.setString(1, answer.id)
|
deleteAnswerPstmt.setString(1, answer.id)
|
||||||
|
29
src/main/scala/lc/core/captchaFields.scala
Normal file
29
src/main/scala/lc/core/captchaFields.scala
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package lc.core
|
||||||
|
|
||||||
|
object ParametersEnum extends Enumeration {
|
||||||
|
type Parameter = Value
|
||||||
|
|
||||||
|
val SUPPORTEDLEVEL: Value = Value("supportedLevels")
|
||||||
|
val SUPPORTEDMEDIA: Value = Value("supportedMedia")
|
||||||
|
val SUPPORTEDINPUTTYPE: Value = Value("supportedInputType")
|
||||||
|
|
||||||
|
val ALLOWEDLEVELS: Value = Value("allowedLevels")
|
||||||
|
val ALLOWEDMEDIA: Value = Value("allowedMedia")
|
||||||
|
val ALLOWEDINPUTTYPE: Value = Value("allowedInputType")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ResultEnum extends Enumeration {
|
||||||
|
type Result = Value
|
||||||
|
|
||||||
|
val TRUE: Value = Value("True")
|
||||||
|
val FALSE: Value = Value("False")
|
||||||
|
val EXPIRED: Value = Value("Expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ErrorMessageEnum extends Enumeration {
|
||||||
|
type ErrorMessage = Value
|
||||||
|
|
||||||
|
val SMW: Value = Value("Oops, something went worng!")
|
||||||
|
val INVALID_PARAM: Value = Value("Invalid Pramaters")
|
||||||
|
val NO_CAPTCHA: Value = Value("No captcha for the provided parameters")
|
||||||
|
}
|
@ -3,6 +3,7 @@ package lc.core
|
|||||||
import lc.captchas._
|
import lc.captchas._
|
||||||
import lc.captchas.interfaces.ChallengeProvider
|
import lc.captchas.interfaces.ChallengeProvider
|
||||||
import lc.captchas.interfaces.Challenge
|
import lc.captchas.interfaces.Challenge
|
||||||
|
import scala.collection.mutable.Map
|
||||||
|
|
||||||
object CaptchaProviders {
|
object CaptchaProviders {
|
||||||
private val providers = Map(
|
private val providers = Map(
|
||||||
@ -21,10 +22,11 @@ object CaptchaProviders {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val seed = System.currentTimeMillis.toString.substring(2, 6).toInt
|
private val seed = Config.seed
|
||||||
private val random = new scala.util.Random(seed)
|
private val random = new scala.util.Random(seed)
|
||||||
|
private val config = Config.captchaConfig
|
||||||
|
|
||||||
private def getNextRandomInt(max: Int) =
|
private def getNextRandomInt(max: Int): Int =
|
||||||
random.synchronized {
|
random.synchronized {
|
||||||
random.nextInt(max)
|
random.nextInt(max)
|
||||||
}
|
}
|
||||||
@ -33,9 +35,31 @@ object CaptchaProviders {
|
|||||||
return providers(id)
|
return providers(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getProvider(): ChallengeProvider = {
|
private def filterProviderByParam(param: Parameters): Iterable[(String, String)] = {
|
||||||
val keys = providers.keys
|
val configFilter = for {
|
||||||
val providerIndex = keys.toVector(getNextRandomInt(keys.size))
|
configValue <- config
|
||||||
providers(providerIndex)
|
if configValue.allowedLevels.contains(param.level)
|
||||||
|
if configValue.allowedMedia.contains(param.media)
|
||||||
|
if configValue.allowedInputType.contains(param.input_type)
|
||||||
|
} yield (configValue.name, configValue.config)
|
||||||
|
|
||||||
|
val providerFilter = for {
|
||||||
|
providerValue <- configFilter
|
||||||
|
providerConfigMap = providers(providerValue._1).supportedParameters()
|
||||||
|
if providerConfigMap.get(ParametersEnum.SUPPORTEDLEVEL.toString).contains(param.level)
|
||||||
|
if providerConfigMap.get(ParametersEnum.SUPPORTEDMEDIA.toString).contains(param.media)
|
||||||
|
if providerConfigMap.get(ParametersEnum.SUPPORTEDINPUTTYPE.toString).contains(param.input_type)
|
||||||
|
} yield (providerValue._1, providerValue._2)
|
||||||
|
|
||||||
|
providerFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
def getProvider(param: Parameters): ChallengeProvider = {
|
||||||
|
val providerConfig = filterProviderByParam(param).toList
|
||||||
|
val randomIndex = getNextRandomInt(providerConfig.length)
|
||||||
|
val providerIndex = providerConfig(randomIndex)._1
|
||||||
|
val selectedProvider = providers(providerIndex)
|
||||||
|
selectedProvider.configure(providerConfig(randomIndex)._2)
|
||||||
|
selectedProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
src/main/scala/lc/core/config.scala
Normal file
48
src/main/scala/lc/core/config.scala
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package lc.core
|
||||||
|
|
||||||
|
import scala.io.Source.fromFile
|
||||||
|
import org.json4s.{DefaultFormats, JValue, JObject, JField, JString}
|
||||||
|
import org.json4s.jackson.JsonMethods.parse
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
|
||||||
|
implicit val formats: DefaultFormats.type = DefaultFormats
|
||||||
|
|
||||||
|
private val configFile = fromFile("config.json")
|
||||||
|
private val configString =
|
||||||
|
try configFile.mkString
|
||||||
|
finally configFile.close
|
||||||
|
private val configJson = parse(configString)
|
||||||
|
|
||||||
|
val port: Int = (configJson \ "port").extract[Int]
|
||||||
|
val throttle: Int = (configJson \ "throttle").extract[Int]
|
||||||
|
val seed: Int = (configJson \ "randomSeed").extract[Int]
|
||||||
|
val captchaExpiryTimeLimit: Int = (configJson \ "captchaExpiryTimeLimit").extract[Int]
|
||||||
|
val threadDelay: Int = (configJson \ "threadDelay").extract[Int]
|
||||||
|
|
||||||
|
private val captchaConfigJson = (configJson \ "captchas")
|
||||||
|
val captchaConfigTransform: JValue = captchaConfigJson transformField {
|
||||||
|
case JField("config", JObject(config)) => ("config", JString(config.toString))
|
||||||
|
}
|
||||||
|
val captchaConfig: List[CaptchaConfig] = captchaConfigTransform.extract[List[CaptchaConfig]]
|
||||||
|
val allowedLevels: Set[String] = getAllValues(configJson, ParametersEnum.ALLOWEDLEVELS.toString)
|
||||||
|
val allowedMedia: Set[String] = getAllValues(configJson, ParametersEnum.ALLOWEDMEDIA.toString)
|
||||||
|
val allowedInputType: Set[String] = getAllValues(configJson, ParametersEnum.ALLOWEDINPUTTYPE.toString)
|
||||||
|
|
||||||
|
private def getAllValues(config: JValue, param: String): Set[String] = {
|
||||||
|
val configValues = (config \\ param)
|
||||||
|
val result = for {
|
||||||
|
JObject(child) <- configValues
|
||||||
|
JField(param) <- child
|
||||||
|
} yield (param)
|
||||||
|
|
||||||
|
var valueSet = Set[String]()
|
||||||
|
for (valueList <- result) {
|
||||||
|
for (value <- valueList._2.children) {
|
||||||
|
valueSet += value.values.toString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valueSet
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,16 @@
|
|||||||
package lc.core
|
package lc.core
|
||||||
|
|
||||||
|
sealed trait ChallengeResult
|
||||||
case class Size(height: Int, width: Int)
|
case class Size(height: Int, width: Int)
|
||||||
case class Parameters(level: String, media: String, input_type: String, size: Option[Size])
|
case class Parameters(level: String, media: String, input_type: String, size: Option[Size])
|
||||||
case class Id(id: String)
|
case class Id(id: String) extends ChallengeResult
|
||||||
|
case class Error(message: String) extends ChallengeResult
|
||||||
case class Answer(answer: String, id: String)
|
case class Answer(answer: String, id: String)
|
||||||
case class Result(result: String)
|
case class Result(result: String)
|
||||||
|
case class CaptchaConfig(
|
||||||
|
name: String,
|
||||||
|
allowedLevels: List[String],
|
||||||
|
allowedMedia: List[String],
|
||||||
|
allowedInputType: List[String],
|
||||||
|
config: String
|
||||||
|
)
|
||||||
|
@ -15,6 +15,8 @@ class Statements(dbConn: DBConn) {
|
|||||||
"secret varchar, " +
|
"secret varchar, " +
|
||||||
"provider varchar, " +
|
"provider varchar, " +
|
||||||
"contentType varchar, " +
|
"contentType varchar, " +
|
||||||
|
"contentLevel varchar, " +
|
||||||
|
"contentInput varchar, " +
|
||||||
"image blob, " +
|
"image blob, " +
|
||||||
"attempted int default 0, " +
|
"attempted int default 0, " +
|
||||||
"PRIMARY KEY(token))"
|
"PRIMARY KEY(token))"
|
||||||
@ -32,8 +34,8 @@ class Statements(dbConn: DBConn) {
|
|||||||
|
|
||||||
val insertPstmt: PreparedStatement = dbConn.con.prepareStatement(
|
val insertPstmt: PreparedStatement = dbConn.con.prepareStatement(
|
||||||
"INSERT INTO " +
|
"INSERT INTO " +
|
||||||
"challenge(id, secret, provider, contentType, image) " +
|
"challenge(id, secret, provider, contentType, contentLevel, contentInput, image) " +
|
||||||
"VALUES (?, ?, ?, ?, ?)",
|
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
Statement.RETURN_GENERATED_KEYS
|
Statement.RETURN_GENERATED_KEYS
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ class Statements(dbConn: DBConn) {
|
|||||||
"SELECT c.secret, c.provider " +
|
"SELECT c.secret, c.provider " +
|
||||||
"FROM challenge c, mapId m " +
|
"FROM challenge c, mapId m " +
|
||||||
"WHERE m.token=c.token AND " +
|
"WHERE m.token=c.token AND " +
|
||||||
"DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, m.lastServed)) > 0 AND " +
|
"DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, ?, m.lastServed)) > 0 AND " +
|
||||||
"m.uuid = ?"
|
"m.uuid = ?"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -71,7 +73,10 @@ class Statements(dbConn: DBConn) {
|
|||||||
val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement(
|
val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement(
|
||||||
"SELECT token " +
|
"SELECT token " +
|
||||||
"FROM challenge " +
|
"FROM challenge " +
|
||||||
"WHERE attempted < 10 " +
|
"WHERE attempted < 10 AND " +
|
||||||
|
"contentLevel = ? AND " +
|
||||||
|
"contentType = ? AND " +
|
||||||
|
"contentInput = ? " +
|
||||||
"ORDER BY RAND() LIMIT 1"
|
"ORDER BY RAND() LIMIT 1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -86,7 +91,7 @@ class Statements(dbConn: DBConn) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val mapIdGCPstmt: PreparedStatement = dbConn.con.prepareStatement(
|
val mapIdGCPstmt: PreparedStatement = dbConn.con.prepareStatement(
|
||||||
"DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, 1, lastServed)) < 0"
|
"DELETE FROM mapId WHERE DATEDIFF(MINUTE, CURRENT_TIMESTAMP, DATEADD(MINUTE, ?, lastServed)) < 0"
|
||||||
)
|
)
|
||||||
|
|
||||||
val getCountChallengeTable: PreparedStatement = dbConn.con.prepareStatement(
|
val getCountChallengeTable: PreparedStatement = dbConn.con.prepareStatement(
|
||||||
|
@ -7,31 +7,32 @@ class QuickStartUser(SequentialTaskSet):
|
|||||||
|
|
||||||
@task
|
@task
|
||||||
def captcha(self):
|
def captcha(self):
|
||||||
captcha_params = {"level":"some","media":"some","input_type":"some"}
|
# TODO: Iterate over parameters for a more comprehensive test
|
||||||
|
captcha_params = {"level":"easy","media":"image/png","input_type":"text"}
|
||||||
|
|
||||||
resp = self.client.post(path="/v1/captcha", json=captcha_params, name="/captcha")
|
resp = self.client.post(path="/v1/captcha", json=captcha_params, name="/captcha")
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
print("\nError on /captcha endpoint: ")
|
print("\nError on /captcha endpoint: ")
|
||||||
print(resp)
|
print(resp)
|
||||||
print(resp.text)
|
print(resp.text)
|
||||||
print("----------------END.C-------------------\n\n")
|
print("----------------END.CAPTCHA-------------------\n\n")
|
||||||
|
|
||||||
uuid = json.loads(resp.text).get("id")
|
uuid = json.loads(resp.text).get("id")
|
||||||
answerBody = {"answer": "qwer123","id": uuid}
|
answerBody = {"answer": "qwer123","id": uuid}
|
||||||
|
|
||||||
resp = self.client.get(path="/v1/media?id=%s" % uuid, name="/media")
|
resp = self.client.get(path="/v1/media?id=%s" % uuid, name="/media")
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
print("\nError on /captcha endpoint: ")
|
print("\nError on /media endpoint: ")
|
||||||
print(resp)
|
print(resp)
|
||||||
print(resp.text)
|
print(resp.text)
|
||||||
print("----------------END.C-------------------\n\n")
|
print("----------------END.MEDIA-------------------\n\n")
|
||||||
|
|
||||||
resp = self.client.post(path='/v1/answer', json=answerBody, name="/answer")
|
resp = self.client.post(path='/v1/answer', json=answerBody, name="/answer")
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
print("\nError on /captcha endpoint: ")
|
print("\nError on /answer endpoint: ")
|
||||||
print(resp)
|
print(resp)
|
||||||
print(resp.text)
|
print(resp.text)
|
||||||
print("----------------END.C-------------------\n\n")
|
print("----------------END.ANSWER-------------------\n\n")
|
||||||
|
|
||||||
|
|
||||||
class User(FastHttpUser):
|
class User(FastHttpUser):
|
||||||
|
Loading…
Reference in New Issue
Block a user