Fix issue in GC (#54)

* Update sql to map uuid to token

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Fix millis to secs conversion

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add synchronisation to media enpoint DB access

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Change error code for rate limiter

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* move prepared statements to Thread Local Storage

* Change test end points

* init GC

* Add GC

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Change status return

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Auto generate token in db

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Remove user management and rate limiting

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add seed for random number generator

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Store random instance as class member

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Update locustfile

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add API documentation

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Move updateTimeStamp to getChallenge methdod
Remove user tables for the DB

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Update Timestamp when creating mapId entry

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Add request method type

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Minor fixes

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Fix issue in GC

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Change db directory

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Update locust test

Signed-off-by: Rahul Rudragoudar <rr83019@gmail.com>

* Update .gitignore
This commit is contained in:
Rahul Rudragoudar 2021-02-16 16:02:57 +05:30 committed by GitHub
parent 5c3bdfeb83
commit b16c2698d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 65 additions and 61 deletions

6
.gitignore vendored
View File

@ -1,10 +1,10 @@
/*.log /*.log
/*.png /*.png
/*.db **/*.db
/bin/ /bin/
/project/target/ /project/**
/project/project/
/target/ /target/
**__pycache__
# for various captcha # for various captcha
/known/ /known/

View File

@ -3,7 +3,7 @@ package lc
import java.sql._ import java.sql._
class DBConn(){ class DBConn(){
val con: Connection = DriverManager.getConnection("jdbc:h2:./captcha", "sa", "") val con: Connection = DriverManager.getConnection("jdbc:h2:./data/H2/captcha", "sa", "")
def getStatement(): Statement = { def getStatement(): Statement = {
con.createStatement() con.createStatement()

View File

@ -36,10 +36,13 @@ object CaptchaProviders {
class Statements(dbConn: DBConn) { class Statements(dbConn: DBConn) {
val insertPstmt = dbConn.con.prepareStatement("INSERT INTO challenge(id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS ) 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 mapPstmt = dbConn.con.prepareStatement("INSERT INTO mapId(uuid, token, lastServed) VALUES (?, ?, CURRENT_TIMESTAMP)")
val selectPstmt = dbConn.con.prepareStatement("SELECT secret, provider FROM challenge WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ? AND DATEDIFF(MINUTE, DATEADD(MINUTE,2,m.lastServed), CURRENT_TIMESTAMP) <= 0)") 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 imagePstmt = dbConn.con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?")
val updateSolvedPstmt = dbConn.con.prepareStatement("UPDATE challenge SET solved = solved+1 WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.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 solved < 10 ORDER BY RAND() LIMIT 1") 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 { object Statements {
@ -51,7 +54,7 @@ class Captcha(throttle: Int, dbConn: DBConn) {
import CaptchaProviders._ import CaptchaProviders._
private val stmt = dbConn.getStatement() 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, solved int default 0, PRIMARY KEY(token))") 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)") 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 seed = System.currentTimeMillis.toString.substring(2,6).toInt
@ -75,16 +78,16 @@ class Captcha(throttle: Int, dbConn: DBConn) {
imagePstmt.setString(1, id.id) imagePstmt.setString(1, id.id)
val rs: ResultSet = imagePstmt.executeQuery() val rs: ResultSet = imagePstmt.executeQuery()
if(rs.next()){ if(rs.next()){
blob = rs.getBlob("image") blob = rs.getBlob("image")
if(blob != null) if(blob != null){
image = blob.getBytes(1, blob.length().toInt) image = blob.getBytes(1, blob.length().toInt)
image }
}
image
} catch { case e: Exception =>
println(e)
image
} }
image
} catch{ case e: Exception =>
println(e)
image
}
} }
private val uniqueIntCount = new AtomicInteger() private val uniqueIntCount = new AtomicInteger()
@ -112,19 +115,23 @@ class Captcha(throttle: Int, dbConn: DBConn) {
val task = new Runnable { val task = new Runnable {
def run(): Unit = { def run(): Unit = {
try { try {
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
}
val gcStmt = stmt.executeUpdate("DELETE FROM challenge WHERE solved > 10 AND token = (SELECT m.token FROM mapId m, challenge c WHERE c.token = m.token AND m.lastServed = (SELECT MAX(m.lastServed) FROM mapId m, challenge c WHERE c.token=m.token AND DATEDIFF(MINUTE, DATEADD(MINUTE,5,m.lastServed), CURRENT_TIMESTAMP) <= 0))")
} catch { case e: Exception => println(e) } 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) }
} }
} }
@ -142,7 +149,11 @@ class Captcha(throttle: Int, dbConn: DBConn) {
} else { } else {
None None
} }
Id(getUUID(tokenOpt.getOrElse(generateChallenge(param)))) 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 => } catch {case e: Exception =>
println(e) println(e)
Id(getUUID(-1)) Id(getUUID(-1))
@ -166,30 +177,26 @@ class Captcha(throttle: Int, dbConn: DBConn) {
val secret = rs.getString("secret") val secret = rs.getString("secret")
val provider = rs.getString("provider") val provider = rs.getString("provider")
val check = providers(provider).checkAnswer(secret, answer.answer) val check = providers(provider).checkAnswer(secret, answer.answer)
val result = if(check) { val result = if(check) "TRUE" else "FALSE"
val updateSolvedPstmt = Statements.tlStmts.get.updateSolvedPstmt
updateSolvedPstmt.setString(1,answer.id)
updateSolvedPstmt.executeUpdate()
"TRUE"
} else {
"FALSE"
}
result result
} else { } else {
"EXPIRED" "EXPIRED"
} }
val deleteAnswerPstmt = Statements.tlStmts.get.deleteAnswerPstmt
deleteAnswerPstmt.setString(1, answer.id)
deleteAnswerPstmt.executeUpdate()
psOpt psOpt
} }
def display(): Unit = { def display(): Unit = {
val rs: ResultSet = stmt.executeQuery("SELECT * FROM challenge") val rs: ResultSet = stmt.executeQuery("SELECT * FROM challenge")
println("token\t\tid\t\tsecret\t\tsolved") println("token\t\tid\t\tsecret\t\tattempted")
while(rs.next()) { while(rs.next()) {
val token = rs.getInt("token") val token = rs.getInt("token")
val id = rs.getString("id") val id = rs.getString("id")
val secret = rs.getString("secret") val secret = rs.getString("secret")
val solved = rs.getString("solved") val attempted = rs.getString("attempted")
println(s"${token}\t\t${id}\t\t${secret}\t\t${solved}\n\n") println(s"${token}\t\t${id}\t\t${secret}\t\t${attempted}\n\n")
} }
val rss: ResultSet = stmt.executeQuery("SELECT * FROM mapId") val rss: ResultSet = stmt.executeQuery("SELECT * FROM mapId")

View File

@ -26,14 +26,15 @@ class Server(port: Int, captcha: Captcha, dbConn: DBConn){
},"POST") },"POST")
host.addContext("/v1/media",(req, resp) => { host.addContext("/v1/media",(req, resp) => {
var id = Id(null) val id = if ("GET" == req.getMethod()){
if ("GET" == req.getMethod()){
val params = req.getParams() val params = req.getParams()
id = Id(params.get("id")) val gid = Id(params.get("id"))
gid
} else { } else {
val body = req.getJson() val body = req.getJson()
val json = parse(body) val json = parse(body)
id = json.extract[Id] val gid = json.extract[Id]
gid
} }
val image = captcha.getCaptcha(id) val image = captcha.getCaptcha(id)
resp.getHeaders().add("Content-Type","image/png") resp.getHeaders().add("Content-Type","image/png")

View File

@ -1,41 +1,37 @@
from locust import task, between, SequentialTaskSet from locust import task, between, SequentialTaskSet
from locust.contrib.fasthttp import FastHttpUser from locust.contrib.fasthttp import FastHttpUser
import json import json
import uuid
class QuickStartUser(SequentialTaskSet): class QuickStartUser(SequentialTaskSet):
wait_time = between(0.1,1) wait_time = between(0.1,1)
captcha_params = {"level":"some","media":"some","input_type":"some"}
answerBody = {"answer": "qwer123"}
@task @task
def captcha(self): def captcha(self):
resp = self.client.post(path="/v1/captcha", json=self.captcha_params, name="/captcha") captcha_params = {"level":"some","media":"some","input_type":"some"}
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.C-------------------\n\n")
self.answerBody["id"] = json.loads(resp.text).get("id")
uuid = json.loads(resp.text).get("id")
answerBody = {"answer": "qwer123","id": uuid}
@task resp = self.client.get(path="/v1/media?id=%s" % uuid, name="/media")
def media(self):
resp = self.client.get(path="/v1/media?id=%s" % self.answerBody.get("id"), name="/media")
if resp.status_code != 200: if resp.status_code != 200:
print("\nError on /media endpoint: ") print("\nError on /captcha endpoint: ")
print(resp) print(resp)
print(resp.text) print(resp.text)
print("-----------------END.M-------------------\n\n") print("----------------END.C-------------------\n\n")
@task resp = self.client.post(path='/v1/answer', json=answerBody, name="/answer")
def answer(self):
resp = self.client.post(path='/v1/answer', json=self.answerBody, name="/answer")
if resp.status_code != 200: if resp.status_code != 200:
print("\nError on /answer endpoint: ") print("\nError on /captcha endpoint: ")
print(resp) print(resp)
print(resp.text) print(resp.text)
print("-------------------END.A---------------\n\n") print("----------------END.C-------------------\n\n")
class User(FastHttpUser): class User(FastHttpUser):