Skip to content

Commit

Permalink
Allow pass in of remote IP
Browse files Browse the repository at this point in the history
  • Loading branch information
wusatosi committed Mar 29, 2023
1 parent 442ba8d commit d068eec
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/com/wusatosi/recaptcha/RecaptchaClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.io.Closeable
interface RecaptchaClient : Closeable {

@Throws(RecaptchaError::class)
suspend fun verify(token: String): Boolean
suspend fun verify(token: String, remoteIp: String = ""): Boolean

companion object {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ internal abstract class RecaptchaClientBase(
private val client: HttpClient = HttpClient(engine) {}
private val validateHost = if (!useRecaptchaDotNetEndPoint) DEFAULT_DOMAIN else ALTERNATE_DOMAIN

protected suspend fun transact(token: String): JsonObject {
val response = executeRequest(token)
protected suspend fun transact(token: String, remoteIp: String): JsonObject {
val response = executeRequest(token, remoteIp)
checkResponseStatus(response)
try {
val body = JsonParser.parseString(response.bodyAsText())
Expand All @@ -60,14 +60,16 @@ internal abstract class RecaptchaClientBase(
throw UnexpectedError("Invalid respond status code: ${status.value}, ${status.description}", null)
}

private suspend fun executeRequest(token: String) = try {
private suspend fun executeRequest(token: String, remoteIp: String) = try {
client.post {
url {
protocol = URLProtocol.HTTPS
host = validateHost
path(VALIDATION_PATH)
parameters.append("secret", secretKey)
parameters.append("response", token)
if (remoteIp.isNotEmpty())
parameters.append("remoteip", remoteIp)
}
}
} catch (io: IOException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ internal class RecaptchaV2ClientImpl(
engine: HttpClientEngine
) : RecaptchaClientBase(secretKey, useRecaptchaDotNetEndPoint, engine), RecaptchaV2Client {

override suspend fun verify(token: String): Boolean {
override suspend fun verify(token: String, remoteIp: String): Boolean {
if (!likelyValidRecaptchaParameter(token))
return false

val response = transact(token)
val response = transact(token, remoteIp)
val (isSuccess, _) = interpretResponseBody(response)
return isSuccess
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ internal class RecaptchaV3ClientImpl(
override suspend fun getVerifyScore(
token: String,
invalidateTokenScore: Double,
timeoutOrDuplicateScore: Double
timeoutOrDuplicateScore: Double,
remoteIp: String
): Double {
if (!likelyValidRecaptchaParameter(token)) return invalidateTokenScore

val response = transact(token)
val response = transact(token, remoteIp)
val (isSuccess, errorCodes) = interpretResponseBody(response)
return if (isSuccess) {
response[SCORE_ATTRIBUTE]
Expand All @@ -43,6 +44,6 @@ internal class RecaptchaV3ClientImpl(
throw UnexpectedError("unexpected error codes: $errorCodes")
}

override suspend fun verify(token: String): Boolean = getVerifyScore(token) > defaultScoreThreshold
override suspend fun verify(token: String, remoteIp: String): Boolean = getVerifyScore(token) > defaultScoreThreshold

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ internal class UniversalRecaptchaClientImpl(
engine: HttpClientEngine
) : RecaptchaClientBase(secretKey, useRecaptchaDotNetEndPoint, engine), RecaptchaClient {

override suspend fun verify(token: String): Boolean {
override suspend fun verify(token: String, remoteIp: String): Boolean {
if (!likelyValidRecaptchaParameter(token))
return false

val response = transact(token)
val response = transact(token, remoteIp)
val (isSuccess, _) = interpretResponseBody(response)

val score = response["score"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ interface RecaptchaV3Client : RecaptchaClient {
suspend fun getVerifyScore(
token: String,
invalidateTokenScore: Double = -1.0,
timeoutOrDuplicateScore: Double = -2.0
timeoutOrDuplicateScore: Double = -2.0,
remoteIp: String = ""
): Double

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class RecaptchaClientBaseTest {
engine: HttpClientEngine,
siteKey: String = "key",
token: String = "token",
useRecaptchaDotNetEndPoint: Boolean = false
useRecaptchaDotNetEndPoint: Boolean = false,
remoteIp: String = ""
): JsonObject? {
var result: JsonObject? = null

Expand All @@ -32,19 +33,19 @@ class RecaptchaClientBaseTest {
useRecaptchaDotNetEndPoint: Boolean,
) :
RecaptchaClientBase(siteKey, useRecaptchaDotNetEndPoint, engine) {
override suspend fun verify(token: String): Boolean {
result = transact(token)
override suspend fun verify(token: String, remoteIp: String): Boolean {
result = transact(token, remoteIp)
return true
}
}

Subject(engine, siteKey, useRecaptchaDotNetEndPoint).use { it.verify(token) }
Subject(engine, siteKey, useRecaptchaDotNetEndPoint).use { it.verify(token, remoteIp) }
return result
}

private fun simulateInterpretBody(body: String): Pair<Boolean, List<String>> {
class Subject : RecaptchaClientBase("", false, MockEngine { respondOk() }) {
override suspend fun verify(token: String): Boolean {
override suspend fun verify(token: String, remoteIp: String): Boolean {
return true
}

Expand Down Expand Up @@ -74,6 +75,42 @@ class RecaptchaClientBaseTest {
simulateVerify(mockEngine, siteKey, token)
}

@Test
fun properParameters_withV4Ip() =
runBlocking<Unit> {
val siteKey = "key"
val token = "token"
val remoteIpV4 = "1.2.3.4"

val mockEngine = MockEngine {
assertEquals("www.google.com", it.url.host)
assertEquals("/recaptcha/api/siteverify", it.url.encodedPath)
assertEquals("secret=$siteKey&response=$token&remoteip=$remoteIpV4", it.url.encodedQuery)
assertEquals(HttpMethod.Post, it.method)
respondOk("{}")
}

simulateVerify(mockEngine, siteKey, token, remoteIp = remoteIpV4)
}

@Test
fun properParameters_withV6Ip() =
runBlocking<Unit> {
val siteKey = "key"
val token = "token"
val remoteIpV6 = "1111:2222:3333:4444:5555:6666:7777:8888"

val mockEngine = MockEngine {
assertEquals("www.google.com", it.url.host)
assertEquals("/recaptcha/api/siteverify", it.url.encodedPath)
assertEquals("secret=$siteKey&response=$token&remoteip=$remoteIpV6", it.url.encodedQuery)
assertEquals(HttpMethod.Post, it.method)
respondOk("{}")
}

simulateVerify(mockEngine, siteKey, token, remoteIp = remoteIpV6)
}

@Test
fun correctParsing() = runBlocking {
val exampleReturn = """
Expand Down

0 comments on commit d068eec

Please sign in to comment.