From b16c2698d28e613ba5267ba3b778bbd9842a31b9 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Tue, 16 Feb 2021 16:02:57 +0530 Subject: [PATCH] Fix issue in GC (#54) * Update sql to map uuid to token Signed-off-by: Rahul Rudragoudar * Fix millis to secs conversion Signed-off-by: Rahul Rudragoudar * Add synchronisation to media enpoint DB access Signed-off-by: Rahul Rudragoudar * Change error code for rate limiter Signed-off-by: Rahul Rudragoudar * move prepared statements to Thread Local Storage * Change test end points * init GC * Add GC Signed-off-by: Rahul Rudragoudar * Change status return Signed-off-by: Rahul Rudragoudar * Auto generate token in db Signed-off-by: Rahul Rudragoudar * Remove user management and rate limiting Signed-off-by: Rahul Rudragoudar * Add seed for random number generator Signed-off-by: Rahul Rudragoudar * Store random instance as class member Signed-off-by: Rahul Rudragoudar * Update locustfile Signed-off-by: Rahul Rudragoudar * Add API documentation Signed-off-by: Rahul Rudragoudar * Move updateTimeStamp to getChallenge methdod Remove user tables for the DB Signed-off-by: Rahul Rudragoudar * Update Timestamp when creating mapId entry Signed-off-by: Rahul Rudragoudar * Add request method type Signed-off-by: Rahul Rudragoudar * Minor fixes Signed-off-by: Rahul Rudragoudar * Fix issue in GC Signed-off-by: Rahul Rudragoudar * Change db directory Signed-off-by: Rahul Rudragoudar * Update locust test Signed-off-by: Rahul Rudragoudar * Update .gitignore --- .gitignore | 6 +-- src/main/scala/lc/DB.scala | 2 +- src/main/scala/lc/Main.scala | 81 ++++++++++++++++++---------------- src/main/scala/lc/Server.scala | 9 ++-- tests/locustfile.py | 28 +++++------- 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 07984a9..2bc6a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ /*.log /*.png -/*.db +**/*.db /bin/ -/project/target/ -/project/project/ +/project/** /target/ +**__pycache__ # for various captcha /known/ diff --git a/src/main/scala/lc/DB.scala b/src/main/scala/lc/DB.scala index e444e4f..cf1641b 100644 --- a/src/main/scala/lc/DB.scala +++ b/src/main/scala/lc/DB.scala @@ -3,7 +3,7 @@ package lc import java.sql._ 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 = { con.createStatement() diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 1cc71fa..96e64ac 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -36,10 +36,13 @@ object CaptchaProviders { 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 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 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 tokenPstmt = dbConn.con.prepareStatement("SELECT token FROM challenge WHERE solved < 10 ORDER BY RAND() LIMIT 1") + 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 { @@ -51,7 +54,7 @@ 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, 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)") 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) val rs: ResultSet = imagePstmt.executeQuery() if(rs.next()){ - blob = rs.getBlob("image") - if(blob != null) - image = blob.getBytes(1, blob.length().toInt) - image + blob = rs.getBlob("image") + if(blob != null){ + image = blob.getBytes(1, blob.length().toInt) + } + } + image + } catch { case e: Exception => + println(e) + image } - image - } catch{ case e: Exception => - println(e) - image - } } private val uniqueIntCount = new AtomicInteger() @@ -112,19 +115,23 @@ class Captcha(throttle: Int, dbConn: DBConn) { val task = new Runnable { def run(): Unit = { - 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))") + try { - } 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 { 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 => println(e) Id(getUUID(-1)) @@ -166,30 +177,26 @@ class Captcha(throttle: Int, dbConn: DBConn) { val secret = rs.getString("secret") val provider = rs.getString("provider") val check = providers(provider).checkAnswer(secret, answer.answer) - val result = if(check) { - val updateSolvedPstmt = Statements.tlStmts.get.updateSolvedPstmt - updateSolvedPstmt.setString(1,answer.id) - updateSolvedPstmt.executeUpdate() - "TRUE" - } else { - "FALSE" - } + 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\tsolved") + 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 solved = rs.getString("solved") - println(s"${token}\t\t${id}\t\t${secret}\t\t${solved}\n\n") + 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") diff --git a/src/main/scala/lc/Server.scala b/src/main/scala/lc/Server.scala index 863a368..37ca396 100644 --- a/src/main/scala/lc/Server.scala +++ b/src/main/scala/lc/Server.scala @@ -26,14 +26,15 @@ class Server(port: Int, captcha: Captcha, dbConn: DBConn){ },"POST") host.addContext("/v1/media",(req, resp) => { - var id = Id(null) - if ("GET" == req.getMethod()){ + val id = if ("GET" == req.getMethod()){ val params = req.getParams() - id = Id(params.get("id")) + val gid = Id(params.get("id")) + gid } else { val body = req.getJson() val json = parse(body) - id = json.extract[Id] + val gid = json.extract[Id] + gid } val image = captcha.getCaptcha(id) resp.getHeaders().add("Content-Type","image/png") diff --git a/tests/locustfile.py b/tests/locustfile.py index 9cf8fa9..d3a72b0 100644 --- a/tests/locustfile.py +++ b/tests/locustfile.py @@ -1,41 +1,37 @@ from locust import task, between, SequentialTaskSet from locust.contrib.fasthttp import FastHttpUser import json -import uuid class QuickStartUser(SequentialTaskSet): wait_time = between(0.1,1) - captcha_params = {"level":"some","media":"some","input_type":"some"} - answerBody = {"answer": "qwer123"} - @task 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: print("\nError on /captcha endpoint: ") print(resp) print(resp.text) 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 - def media(self): - resp = self.client.get(path="/v1/media?id=%s" % self.answerBody.get("id"), name="/media") + resp = self.client.get(path="/v1/media?id=%s" % uuid, name="/media") if resp.status_code != 200: - print("\nError on /media endpoint: ") + print("\nError on /captcha endpoint: ") print(resp) print(resp.text) - print("-----------------END.M-------------------\n\n") + print("----------------END.C-------------------\n\n") - @task - def answer(self): - resp = self.client.post(path='/v1/answer', json=self.answerBody, name="/answer") + resp = self.client.post(path='/v1/answer', json=answerBody, name="/answer") if resp.status_code != 200: - print("\nError on /answer endpoint: ") + print("\nError on /captcha endpoint: ") print(resp) print(resp.text) - print("-------------------END.A---------------\n\n") + print("----------------END.C-------------------\n\n") class User(FastHttpUser):