Merge pull request #148 from librecaptcha/fix145

Fix for #145: frequent repetition of CAPTCHA challenge
This commit is contained in:
hrj 2022-03-01 11:12:55 +05:30 committed by GitHub
commit 226d3bfa74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 17 deletions

View File

@ -18,22 +18,37 @@ class BackgroundTask(config: Config, captchaManager: CaptchaManager) {
val challengeGCPstmt = Statements.tlStmts.get.challengeGCPstmt
challengeGCPstmt.executeUpdate()
val imageNumResult = Statements.tlStmts.get.getCountChallengeTable.executeQuery()
val imageNum = if (imageNumResult.next()) {
imageNumResult.getInt("total")
} else {
0
}
val allCombinations = allParameterCombinations()
val requiredCountPerCombination = Math.max(1, (config.throttle * 1.01) / allCombinations.size).toInt
val throttle = (config.throttle * 1.1).toInt - imageNum
for (param <- allCombinations) {
val countExisting = captchaManager.getCount(param).getOrElse(0)
val countRequired = requiredCountPerCombination - countExisting
if (countRequired > 0) {
val countCreate = Math.min(1.0 + requiredCountPerCombination/10.0, countRequired).toInt
println(s"Creating $countCreate of $countRequired captchas for $param")
for (i <- 0 until throttle) {
captchaManager.generateChallenge(getRandomParam())
for (i <- 0 until countCreate) {
captchaManager.generateChallenge(param)
}
}
}
} catch { case exception: Exception => println(exception) }
}
}
private def allParameterCombinations(): List[Parameters] = {
(config.captchaConfig).flatMap {captcha =>
(captcha.allowedLevels).flatMap {level =>
(captcha.allowedMedia).flatMap {media =>
(captcha.allowedInputType).map {inputType =>
Parameters(level, media, inputType, Some(Size(0, 0)))
}
}
}
}
}
private def getRandomParam(): Parameters = {
val captcha = pickRandom(config.captchaConfig)
val level = pickRandom(captcha.allowedLevels)

View File

@ -105,19 +105,38 @@ class CaptchaManager(config: Config, captchaProviders: CaptchaProviders) {
}
}
private def getToken(param: Parameters): Option[Int] = {
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
tokenPstmt.setString(1, param.level)
tokenPstmt.setString(2, param.media)
tokenPstmt.setString(3, param.input_type)
val rs = tokenPstmt.executeQuery()
def getCount(param: Parameters): Option[Int] = {
val countPstmt = Statements.tlStmts.get.countForParameterPstmt
countPstmt.setString(1, param.level)
countPstmt.setString(2, param.media)
countPstmt.setString(3, param.input_type)
val rs = countPstmt.executeQuery()
if (rs.next()) {
Some(rs.getInt("token"))
Some(rs.getInt("count"))
} else {
None
}
}
private def getToken(param: Parameters): Option[Int] = {
val count = getCount(param).getOrElse(0)
if (count == 0) {
None
} else {
val tokenPstmt = Statements.tlStmts.get.tokenPstmt
tokenPstmt.setString(1, param.level)
tokenPstmt.setString(2, param.media)
tokenPstmt.setString(3, param.input_type)
tokenPstmt.setInt(4, count)
val rs = tokenPstmt.executeQuery()
if (rs.next()) {
Some(rs.getInt("token"))
} else {
None
}
}
}
private def updateAttempted(token: Int): Unit = {
val updateAttemptedPstmt = Statements.tlStmts.get.updateAttemptedPstmt
updateAttemptedPstmt.setInt(1, token)

View File

@ -70,6 +70,17 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
"WHERE token = ?;"
)
val countForParameterPstmt: PreparedStatement = dbConn.con.prepareStatement(
s"""
SELECT count(*) as count
FROM challenge
WHERE attempted < $maxAttempts AND
contentLevel = ? AND
contentType = ? AND
contentInput = ?
"""
)
val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement(
s"""
SELECT token, attempted
@ -78,7 +89,9 @@ class Statements(dbConn: DBConn, maxAttempts: Int) {
contentLevel = ? AND
contentType = ? AND
contentInput = ?
ORDER BY attempted ASC LIMIT 1"""
LIMIT 1
OFFSET FLOOR(RAND()*?)
"""
)
val deleteAnswerPstmt: PreparedStatement = dbConn.con.prepareStatement(