Repo Created
This commit is contained in:
parent
eb305e2886
commit
a8c22c65db
4784 changed files with 329907 additions and 2 deletions
41
play-services-recaptcha/build.gradle
Normal file
41
play-services-recaptcha/build.gradle
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
android {
|
||||
namespace "com.google.android.gms.recaptcha"
|
||||
|
||||
compileSdkVersion androidCompileSdk
|
||||
buildToolsVersion "$androidBuildVersionTools"
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionName version
|
||||
minSdkVersion androidMinSdk
|
||||
targetSdkVersion androidTargetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
||||
description = 'microG implementation of play-services-recaptcha'
|
||||
|
||||
dependencies {
|
||||
// Dependencies from play-services-recaptcha:17.0.1
|
||||
api project(':play-services-base')
|
||||
api project(':play-services-basement')
|
||||
api project(':play-services-tasks')
|
||||
}
|
||||
68
play-services-recaptcha/core/build.gradle
Normal file
68
play-services-recaptcha/core/build.gradle
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'com.squareup.wire'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
implementation project(':play-services-base-core')
|
||||
implementation project(':play-services-droidguard-core')
|
||||
implementation project(':play-services-safetynet-core')
|
||||
implementation project(':play-services-recaptcha')
|
||||
implementation project(':play-services-tasks-ktx')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
|
||||
|
||||
implementation "androidx.core:core-ktx:$coreVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
implementation "androidx.webkit:webkit:$webkitVersion"
|
||||
|
||||
implementation "com.android.volley:volley:$volleyVersion"
|
||||
implementation "com.squareup.wire:wire-runtime:$wireVersion"
|
||||
}
|
||||
|
||||
wire {
|
||||
kotlin {}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "org.microg.gms.recaptcha.core"
|
||||
|
||||
compileSdkVersion androidCompileSdk
|
||||
buildToolsVersion "$androidBuildVersionTools"
|
||||
|
||||
defaultConfig {
|
||||
versionName version
|
||||
minSdkVersion androidMinSdk
|
||||
targetSdkVersion androidTargetSdk
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../../gradle/publish-android.gradle'
|
||||
|
||||
description = 'microG service implementation for play-services-recaptcha'
|
||||
18
play-services-recaptcha/core/src/main/AndroidManifest.xml
Normal file
18
play-services-recaptcha/core/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<!-- This service is in :ui process because it may spawn a web view. See https://crbug.com/558377 -->
|
||||
<service android:name="org.microg.gms.recaptcha.RecaptchaService"
|
||||
android:process=":ui">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.recaptcha.service.START"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.recaptcha
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.LocaleList
|
||||
import android.util.Log
|
||||
import com.android.volley.*
|
||||
import com.android.volley.toolbox.Volley
|
||||
import com.google.android.gms.droidguard.DroidGuardClient
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle
|
||||
import com.google.android.gms.recaptcha.RecaptchaResultData
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteParams
|
||||
import com.google.android.gms.recaptcha.internal.InitParams
|
||||
import com.google.android.gms.tasks.await
|
||||
import com.squareup.wire.Message
|
||||
import com.squareup.wire.ProtoAdapter
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import org.microg.gms.droidguard.core.VersionUtil
|
||||
import org.microg.gms.utils.singleInstanceOf
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
private const val TAG = "RecaptchaGuard"
|
||||
|
||||
class RecaptchaGuardImpl(private val context: Context, private val packageName: String) : RecaptchaImpl {
|
||||
private val queue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) }
|
||||
private var lastToken: String? = null
|
||||
|
||||
override suspend fun init(params: InitParams): RecaptchaHandle {
|
||||
val response = ProtobufPostRequest(
|
||||
"https://www.recaptcha.net/recaptcha/api3/ac", RecaptchaInitRequest(
|
||||
data_ = RecaptchaInitRequest.Data(
|
||||
siteKey = params.siteKey,
|
||||
packageName = packageName,
|
||||
version = "${VersionUtil(context).versionCode};${params.version}"
|
||||
)
|
||||
), RecaptchaInitResponse.ADAPTER
|
||||
).sendAndAwait(queue)
|
||||
lastToken = response.token
|
||||
return RecaptchaHandle(params.siteKey, packageName, response.acceptableAdditionalArgs.toList())
|
||||
}
|
||||
|
||||
override suspend fun execute(params: ExecuteParams): RecaptchaResultData {
|
||||
if (params.handle.clientPackageName != null && params.handle.clientPackageName != packageName) throw IllegalArgumentException("invalid handle")
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val additionalArgs = mutableMapOf<String, String>()
|
||||
val guardMap = mutableMapOf<String, String>()
|
||||
for (key in params.action.additionalArgs.keySet()) {
|
||||
val value = params.action.additionalArgs.getString(key)
|
||||
?: throw Exception("Only string values are allowed as an additional arg in RecaptchaAction")
|
||||
if (key !in params.handle.acceptableAdditionalArgs)
|
||||
throw Exception("AdditionalArgs key[ \"$key\" ] is not accepted by reCATPCHA server")
|
||||
additionalArgs.put(key, value)
|
||||
}
|
||||
Log.d(TAG, "Additional arguments: $additionalArgs")
|
||||
if (lastToken == null) {
|
||||
init(InitParams().apply { siteKey = params.handle.siteKey; version = params.version })
|
||||
}
|
||||
val token = lastToken!!
|
||||
guardMap["token"] = token
|
||||
guardMap["action"] = params.action.toString()
|
||||
guardMap["timestamp_millis"] to timestamp.toString()
|
||||
guardMap.putAll(additionalArgs)
|
||||
if (params.action.verificationHistoryToken != null)
|
||||
guardMap["verification_history_token"] = params.action.verificationHistoryToken
|
||||
val dg = DroidGuardClient.getResults(context, "recaptcha-android", guardMap).await()
|
||||
val response = ProtobufPostRequest(
|
||||
"https://www.recaptcha.net/recaptcha/api3/ae", RecaptchaExecuteRequest(
|
||||
token = token,
|
||||
action = params.action.toString(),
|
||||
timestamp = timestamp,
|
||||
dg = dg,
|
||||
additionalArgs = additionalArgs,
|
||||
verificationHistoryToken = params.action.verificationHistoryToken
|
||||
), RecaptchaExecuteResponse.ADAPTER
|
||||
).sendAndAwait(queue)
|
||||
return RecaptchaResultData(response.token)
|
||||
}
|
||||
|
||||
override suspend fun close(handle: RecaptchaHandle): Boolean {
|
||||
if (handle.clientPackageName != null && handle.clientPackageName != packageName) throw IllegalArgumentException("invalid handle")
|
||||
val closed = lastToken != null
|
||||
lastToken = null
|
||||
return closed
|
||||
}
|
||||
}
|
||||
|
||||
class ProtobufPostRequest<I : Message<I, *>, O>(url: String, private val i: I, private val oAdapter: ProtoAdapter<O>) :
|
||||
Request<O>(Request.Method.POST, url, null) {
|
||||
private val deferred = CompletableDeferred<O>()
|
||||
|
||||
override fun getHeaders(): Map<String, String> {
|
||||
val headers = HashMap(super.getHeaders())
|
||||
headers["Accept-Language"] = if (SDK_INT >= 24) LocaleList.getDefault().toLanguageTags() else Locale.getDefault().language
|
||||
return headers
|
||||
}
|
||||
|
||||
override fun getBody(): ByteArray = i.encode()
|
||||
|
||||
override fun getBodyContentType(): String = "application/x-protobuf"
|
||||
|
||||
override fun parseNetworkResponse(response: NetworkResponse): Response<O> {
|
||||
try {
|
||||
return Response.success(oAdapter.decode(response.data), null)
|
||||
} catch (e: VolleyError) {
|
||||
return Response.error(e)
|
||||
} catch (e: Exception) {
|
||||
return Response.error(VolleyError())
|
||||
}
|
||||
}
|
||||
|
||||
override fun deliverResponse(response: O) {
|
||||
Log.d(TAG, "Got response: $response")
|
||||
deferred.complete(response)
|
||||
}
|
||||
|
||||
override fun deliverError(error: VolleyError) {
|
||||
deferred.completeExceptionally(error)
|
||||
}
|
||||
|
||||
suspend fun await(): O = deferred.await()
|
||||
|
||||
suspend fun sendAndAwait(queue: RequestQueue): O {
|
||||
Log.d(TAG, "Sending request: $i")
|
||||
queue.add(this)
|
||||
return await()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.recaptcha
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle
|
||||
import com.google.android.gms.recaptcha.RecaptchaResultData
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteParams
|
||||
import com.google.android.gms.recaptcha.internal.InitParams
|
||||
|
||||
interface RecaptchaImpl {
|
||||
suspend fun init(params: InitParams): RecaptchaHandle
|
||||
suspend fun execute(params: ExecuteParams): RecaptchaResultData
|
||||
suspend fun close(handle: RecaptchaHandle): Boolean
|
||||
|
||||
object Unsupported : RecaptchaImpl {
|
||||
override suspend fun init(params: InitParams): RecaptchaHandle {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override suspend fun execute(params: ExecuteParams): RecaptchaResultData {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override suspend fun close(handle: RecaptchaHandle): Boolean {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.gms.recaptcha
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Parcel
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.gms.common.Feature
|
||||
import com.google.android.gms.common.api.CommonStatusCodes
|
||||
import com.google.android.gms.common.api.Status
|
||||
import com.google.android.gms.common.internal.ConnectionInfo
|
||||
import com.google.android.gms.common.internal.GetServiceRequest
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks
|
||||
import com.google.android.gms.recaptcha.RecaptchaAction
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle
|
||||
import com.google.android.gms.recaptcha.internal.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.microg.gms.BaseService
|
||||
import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.droidguard.core.DroidGuardPreferences
|
||||
import org.microg.gms.safetynet.SafetyNetPreferences
|
||||
import org.microg.gms.utils.warnOnTransactionIssues
|
||||
|
||||
private const val TAG = "RecaptchaService"
|
||||
|
||||
class RecaptchaService : BaseService(TAG, GmsService.RECAPTCHA) {
|
||||
private fun getRecaptchaImpl(packageName: String): ArrayList<RecaptchaImpl> {
|
||||
val list = ArrayList<RecaptchaImpl>()
|
||||
if (SafetyNetPreferences.isEnabled(this) && SDK_INT >= 19) {
|
||||
list.add(RecaptchaWebImpl(this, packageName, lifecycle))
|
||||
}
|
||||
if (DroidGuardPreferences.isAvailable(this)) {
|
||||
list.add(RecaptchaGuardImpl(this, packageName))
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list.add(RecaptchaImpl.Unsupported)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
|
||||
val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName)!!
|
||||
val imps = getRecaptchaImpl(packageName)
|
||||
callback.onPostInitCompleteWithConnectionInfo(
|
||||
CommonStatusCodes.SUCCESS,
|
||||
RecaptchaServiceImpl(this, packageName, lifecycle, imps),
|
||||
ConnectionInfo().apply {
|
||||
features = arrayOf(
|
||||
Feature("verify_with_recaptcha_v2_internal", 1),
|
||||
Feature("init", 3),
|
||||
Feature("execute", 5),
|
||||
Feature("close", 2)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RecaptchaServiceImpl(
|
||||
private val context: Context,
|
||||
private val packageName: String,
|
||||
override val lifecycle: Lifecycle,
|
||||
private val imps: List<RecaptchaImpl>
|
||||
) : IRecaptchaService.Stub(), LifecycleOwner {
|
||||
|
||||
private var realRecaptchaImpl: RecaptchaImpl? = null
|
||||
|
||||
override fun verifyWithRecaptcha(callback: IExecuteCallback, siteKey: String, packageName: String) {
|
||||
Log.d(TAG, "Not yet implemented: verifyWithRecaptcha($siteKey, $packageName)")
|
||||
}
|
||||
|
||||
override fun init(callback: IInitCallback, siteKey: String) {
|
||||
init2(callback, InitParams().also {
|
||||
it.siteKey = siteKey
|
||||
it.version = LEGACY_VERSION
|
||||
})
|
||||
}
|
||||
|
||||
override fun execute(callback: IExecuteCallback, handle: RecaptchaHandle, action: RecaptchaAction) {
|
||||
execute2(callback, ExecuteParams().also {
|
||||
it.handle = handle
|
||||
it.action = action
|
||||
it.version = LEGACY_VERSION
|
||||
})
|
||||
}
|
||||
|
||||
override fun close(callback: ICloseCallback, handle: RecaptchaHandle) {
|
||||
lifecycleScope.launch {
|
||||
Log.d(TAG, "close($handle)")
|
||||
try {
|
||||
if (realRecaptchaImpl == null) {
|
||||
throw UnsupportedOperationException("Method <close> realRecaptchaImpl is null")
|
||||
}
|
||||
Log.d(TAG, "close realRecaptchaImpl:${realRecaptchaImpl}")
|
||||
callback.onClosed(Status.SUCCESS, realRecaptchaImpl!!.close(handle))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun init2(callback: IInitCallback, params: InitParams) {
|
||||
lifecycleScope.launch {
|
||||
Log.d(TAG, "init($params)")
|
||||
try {
|
||||
Log.d(TAG, "imps size: ${imps.size}")
|
||||
for (recaptchaImpl in imps) {
|
||||
Log.d(TAG, "recaptchaImpl:${recaptchaImpl}")
|
||||
val recaptchaHandle = runCatching { recaptchaImpl.init(params) }.getOrNull() ?: continue
|
||||
realRecaptchaImpl = recaptchaImpl
|
||||
if (params.version == LEGACY_VERSION) {
|
||||
callback.onHandle(Status.SUCCESS, recaptchaHandle)
|
||||
} else {
|
||||
callback.onResults(Status.SUCCESS, InitResults().also { it.handle = recaptchaHandle })
|
||||
}
|
||||
Log.d(TAG, "realRecaptchaImpl:${realRecaptchaImpl}")
|
||||
return@launch
|
||||
}
|
||||
if (realRecaptchaImpl == null) {
|
||||
throw UnsupportedOperationException("Method <init2> realRecaptchaImpl is null")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
try {
|
||||
if (params.version == LEGACY_VERSION) {
|
||||
callback.onHandle(Status.INTERNAL_ERROR, null)
|
||||
} else {
|
||||
callback.onResults(Status.INTERNAL_ERROR, InitResults())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute2(callback: IExecuteCallback, params: ExecuteParams) {
|
||||
Log.d(TAG, "execute($params)")
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
if (realRecaptchaImpl == null) {
|
||||
throw UnsupportedOperationException("Method <execute2> realRecaptchaImpl is null")
|
||||
}
|
||||
Log.d(TAG, "execute2 realRecaptchaImpl:${realRecaptchaImpl}")
|
||||
val data = realRecaptchaImpl!!.execute(params)
|
||||
if (params.version == LEGACY_VERSION) {
|
||||
callback.onData(Status.SUCCESS, data)
|
||||
} else {
|
||||
callback.onResults(Status.SUCCESS, ExecuteResults().also { it.data = data })
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
try {
|
||||
if (params.version == LEGACY_VERSION) {
|
||||
callback.onData(Status.INTERNAL_ERROR, null)
|
||||
} else {
|
||||
callback.onResults(Status.INTERNAL_ERROR, ExecuteResults())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean =
|
||||
warnOnTransactionIssues(code, reply, flags, TAG) {
|
||||
super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LEGACY_VERSION = "16.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,736 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.recaptcha
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.AudioManager
|
||||
import android.os.BatteryManager
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
import android.text.format.DateFormat
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.webkit.WebViewClientCompat
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle
|
||||
import com.google.android.gms.recaptcha.RecaptchaResultData
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteParams
|
||||
import com.google.android.gms.recaptcha.internal.InitParams
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.android.gms.tasks.Tasks
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.ByteString
|
||||
import org.microg.gms.profile.Build
|
||||
import org.microg.gms.profile.ProfileManager
|
||||
import org.microg.gms.tasks.TaskImpl
|
||||
import org.microg.gms.utils.toBase64
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.lang.reflect.Array
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.lang.reflect.Proxy
|
||||
import java.net.URLEncoder
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.ArrayDeque
|
||||
import java.util.Locale
|
||||
import java.util.Queue
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private const val TAG = "RecaptchaWeb"
|
||||
|
||||
@RequiresApi(19)
|
||||
class RecaptchaWebImpl(private val context: Context, private val packageName: String, override val lifecycle: Lifecycle) : RecaptchaImpl, LifecycleOwner {
|
||||
private var webView: WebView? = null
|
||||
private var lastRequestToken: String? = null
|
||||
private var initFinished = AtomicBoolean(true)
|
||||
private var initContinuation: Continuation<Int>? = null
|
||||
private var executeFinished = AtomicBoolean(true)
|
||||
private var executeContinuation: Continuation<String>? = null
|
||||
|
||||
override suspend fun init(params: InitParams): RecaptchaHandle {
|
||||
lastRequestToken = UUID.randomUUID().toString()
|
||||
ProfileManager.ensureInitialized(context)
|
||||
FakeHandler.setDecryptKeyPrefix(IntArray(0))
|
||||
FakeApplication.context = context
|
||||
FakeApplication.packageNameOverride = packageName
|
||||
suspendCoroutine { continuation ->
|
||||
initFinished.set(false)
|
||||
initContinuation = continuation
|
||||
webView = WebView(context).apply {
|
||||
settings.javaScriptEnabled = true
|
||||
addJavascriptInterface(RNJavaScriptInterface(this@RecaptchaWebImpl, CodeInterpreter(this@RecaptchaWebImpl)), "RN")
|
||||
webViewClient = object : WebViewClientCompat() {
|
||||
fun String.isRecaptchaUrl() = startsWith("https://www.recaptcha.net/") || startsWith("https://www.gstatic.com/recaptcha/")
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
|
||||
if (url.isRecaptchaUrl()) {
|
||||
return null
|
||||
}
|
||||
return WebResourceResponse("text/plain", "UTF-8", ByteArrayInputStream(byteArrayOf()))
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
return !url.isRecaptchaUrl()
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
}
|
||||
}
|
||||
postUrl(
|
||||
MWV_URL, ("" +
|
||||
"k=${URLEncoder.encode(params.siteKey, "UTF-8")}&" +
|
||||
"pk=${URLEncoder.encode(packageName, "UTF-8")}&" +
|
||||
"mst=ANDROID_ONPLAY&" +
|
||||
"msv=18.1.1&" +
|
||||
"msi=${URLEncoder.encode(lastRequestToken, "UTF-8")}&" +
|
||||
"mov=${Build.VERSION.SDK_INT}"
|
||||
).toByteArray()
|
||||
)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
delay(10000)
|
||||
if (!initFinished.getAndSet(true)) {
|
||||
try {
|
||||
continuation.resumeWithException(RuntimeException("Timeout reached"))
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
initContinuation = null
|
||||
return RecaptchaHandle(params.siteKey, packageName, emptyList())
|
||||
}
|
||||
|
||||
override suspend fun execute(params: ExecuteParams): RecaptchaResultData {
|
||||
if (webView == null) {
|
||||
init(InitParams().apply { siteKey = params.handle.siteKey; version = params.version })
|
||||
}
|
||||
val additionalArgs = mutableMapOf<String, String>()
|
||||
for (key in params.action.additionalArgs.keySet()) {
|
||||
additionalArgs[key] = params.action.additionalArgs.getString(key)!!
|
||||
}
|
||||
val request = RecaptchaExecuteRequest(token = lastRequestToken, action = params.action.toString(), additionalArgs = additionalArgs).encode().toBase64(Base64.URL_SAFE, Base64.NO_WRAP)
|
||||
val token = suspendCoroutine { continuation ->
|
||||
executeFinished.set(false)
|
||||
executeContinuation = continuation
|
||||
eval("recaptcha.m.Main.execute(\"${request}\")")
|
||||
lifecycleScope.launch {
|
||||
delay(10000)
|
||||
if (!executeFinished.getAndSet(true)) {
|
||||
try {
|
||||
continuation.resumeWithException(RuntimeException("Timeout reached"))
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return RecaptchaResultData(token)
|
||||
}
|
||||
|
||||
override suspend fun close(handle: RecaptchaHandle): Boolean {
|
||||
if (handle.clientPackageName != null && handle.clientPackageName != packageName) throw IllegalArgumentException("invalid handle")
|
||||
val closed = webView != null
|
||||
webView?.stopLoading()
|
||||
webView?.loadUrl("about:blank")
|
||||
webView = null
|
||||
return closed
|
||||
}
|
||||
|
||||
private fun eval(script: String) {
|
||||
Log.d(TAG, "eval: $script")
|
||||
webView?.let {
|
||||
Handler(context.mainLooper).post {
|
||||
it.evaluateJavascript(script, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun finalize() {
|
||||
FakeApplication.packageNameOverride = ""
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MWV_URL = "https://www.recaptcha.net/recaptcha/api3/mwv"
|
||||
private const val DEBUG = false
|
||||
object FakeApplication : Application() {
|
||||
var context: Context
|
||||
get() = baseContext
|
||||
set(value) { try { attachBaseContext(value.applicationContext) } catch (_: Exception) { } }
|
||||
var packageNameOverride: String = ""
|
||||
override fun getPackageName(): String {
|
||||
return packageNameOverride
|
||||
}
|
||||
}
|
||||
var codeDecryptKeyPrefix = emptyList<Int>()
|
||||
private set
|
||||
|
||||
class FakeHandler : Exception() {
|
||||
private var cloudProjectNumber: Long? = 0
|
||||
private var nonce: String? = null
|
||||
|
||||
@Keep
|
||||
fun requestIntegrityToken(request: FakeHandler): Task<FakeHandler> {
|
||||
return Tasks.forException(FakeHandler())
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun setCloudProjectNumber(cloudProjectNumber: Long): FakeHandler {
|
||||
this.cloudProjectNumber = cloudProjectNumber
|
||||
return this
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun setNonce(nonce: String): FakeHandler {
|
||||
this.nonce = nonce
|
||||
return this
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun build(): FakeHandler {
|
||||
return this
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun cloudProjectNumber(): Long? {
|
||||
return cloudProjectNumber
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun nonce(): String? {
|
||||
return nonce
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun getErrorCode(): Int = -1
|
||||
|
||||
companion object {
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun setDecryptKeyPrefix(newKeyPrefix: IntArray) {
|
||||
codeDecryptKeyPrefix = newKeyPrefix.asList()
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun getFakeApplication(): Application = FakeApplication
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun createFakeIntegrityManager(context: Context): FakeHandler {
|
||||
return FakeHandler()
|
||||
}
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun createFakeIntegrityTokenRequestBuilder(): FakeHandler {
|
||||
return FakeHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CodeInterpreter(private val impl: RecaptchaWebImpl) {
|
||||
val dict = mutableMapOf<Int, Any?>()
|
||||
var errorHandler = ""
|
||||
var xorSecret = IntRange(0, 127).random().toByte()
|
||||
|
||||
private val intToClassMap = mapOf(
|
||||
1 to java.lang.Integer.TYPE,
|
||||
2 to java.lang.Short.TYPE,
|
||||
3 to java.lang.Byte.TYPE,
|
||||
4 to java.lang.Long.TYPE,
|
||||
5 to java.lang.Character.TYPE,
|
||||
6 to java.lang.Float.TYPE,
|
||||
7 to java.lang.Double.TYPE,
|
||||
8 to java.lang.Boolean.TYPE,
|
||||
9 to FakeHandler::class.java
|
||||
)
|
||||
|
||||
private fun getClass(name: String): Class<*>? = when (name) {
|
||||
"[I" -> IntArray::class.java
|
||||
"[B" -> ByteArray::class.java
|
||||
"android.os.Build" -> Build::class.java
|
||||
"android.os.Build\$VERSION" -> Build.VERSION::class.java
|
||||
"android.app.ActivityThread" -> FakeHandler::class.java
|
||||
"com.google.android.play.core.integrity.IntegrityManager" -> FakeHandler::class.java
|
||||
"com.google.android.play.core.integrity.IntegrityManagerFactory" -> FakeHandler::class.java
|
||||
"com.google.android.play.core.integrity.IntegrityTokenRequest" -> FakeHandler::class.java
|
||||
"com.google.android.play.core.integrity.IntegrityTokenResponse" -> FakeHandler::class.java
|
||||
"android.content.Intent", "android.content.IntentFilter", "android.content.BroadcastReceiver",
|
||||
"android.content.Context", "android.content.pm.PackageManager", "android.content.ContentResolver",
|
||||
"java.lang.String", "java.lang.CharSequence", "java.lang.Long",
|
||||
"java.nio.charset.Charset", "java.nio.charset.StandardCharsets",
|
||||
"android.text.format.DateFormat", "java.util.Date", "java.util.Locale", "java.nio.ByteBuffer",
|
||||
"android.os.BatteryManager", "android.media.AudioManager",
|
||||
"com.google.android.gms.tasks.OnCompleteListener",
|
||||
"android.provider.Settings\$System" -> Class.forName(name)
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Not providing class $name", Exception())
|
||||
if (DEBUG) Class.forName(name) else null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMethod(cls: Class<*>, name: String, params: kotlin.Array<Class<*>?>): Method? = when {
|
||||
cls == FakeHandler::class.java && name == "acx" -> FakeHandler::class.java.getMethod("setDecryptKeyPrefix", *params)
|
||||
cls == FakeHandler::class.java && name == "currentApplication" -> FakeHandler::class.java.getMethod("getFakeApplication", *params)
|
||||
cls == FakeHandler::class.java && name == "create" -> FakeHandler::class.java.getMethod("createFakeIntegrityManager", *params)
|
||||
cls == FakeHandler::class.java && name == "builder" -> FakeHandler::class.java.getMethod("createFakeIntegrityTokenRequestBuilder", *params)
|
||||
cls == FakeHandler::class.java -> cls.getMethod(name, *params)
|
||||
cls == FakeApplication.javaClass && name == "getContentResolver" -> cls.getMethod(name, *params)
|
||||
cls == FakeApplication.javaClass && name == "getSystemService" -> cls.getMethod(name, *params)
|
||||
cls == FakeApplication.javaClass && name == "registerReceiver" -> cls.getMethod(name, *params)
|
||||
cls == PackageManager::class.java && name == "checkPermission" -> cls.getMethod(name, *params)
|
||||
cls == Context::class.java && name == "checkSelfPermission" -> cls.getMethod(name, *params)
|
||||
cls == Context::class.java && name == "getPackageManager" -> cls.getMethod(name, *params)
|
||||
cls == Context::class.java && name == "getPackageName" -> cls.getMethod(name, *params)
|
||||
cls == AudioManager::class.java && name == "getStreamVolume" -> cls.getMethod(name, *params)
|
||||
cls == Settings.System::class.java && name == "getInt" -> cls.getMethod(name, *params)
|
||||
cls == DateFormat::class.java -> cls.getMethod(name, *params)
|
||||
cls == Locale::class.java -> cls.getMethod(name, *params)
|
||||
cls == Intent::class.java -> cls.getMethod(name, *params)
|
||||
cls == String::class.java -> cls.getMethod(name, *params)
|
||||
cls == ByteBuffer::class.java -> cls.getMethod(name, *params)
|
||||
cls == TaskImpl::class.java -> cls.getMethod(name, *params)
|
||||
name == "toString" -> cls.getMethod(name, *params)
|
||||
name == "parseLong" -> cls.getMethod(name, *params)
|
||||
else -> {
|
||||
Log.w(TAG, "Not providing method $name in ${cls.display()}", Exception())
|
||||
if (DEBUG) cls.getMethod(name, *params) else null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getField(cls: Class<*>, name: String): Field? = when {
|
||||
cls == Build::class.java -> cls.getField(name)
|
||||
cls == Build.VERSION::class.java -> cls.getField(name)
|
||||
cls == Settings.System::class.java && cls.getField(name).modifiers.and(Modifier.STATIC) > 0 -> cls.getField(name)
|
||||
cls == BatteryManager::class.java && cls.getField(name).modifiers.and(Modifier.STATIC) > 0 -> cls.getField(name)
|
||||
cls == AudioManager::class.java && cls.getField(name).modifiers.and(Modifier.STATIC) > 0 -> cls.getField(name)
|
||||
cls == StandardCharsets::class.java && cls.getField(name).modifiers.and(Modifier.STATIC) > 0 -> cls.getField(name)
|
||||
else -> {
|
||||
Log.w(TAG, "Not providing field $name in ${cls.display()}", Exception())
|
||||
if (DEBUG) cls.getField(name) else null
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun Any?.rem(other: Any?): Any? = when {
|
||||
this is IntArray && other is Int -> map { it % other }.toIntArray()
|
||||
else -> throw UnsupportedOperationException("rem ${this?.javaClass} % ${other?.javaClass}")
|
||||
}
|
||||
|
||||
private infix fun Any?.xor(other: Any?): Any? = when {
|
||||
this is String && other is Int -> map { it.code xor other }.toIntArray()
|
||||
this is String && other is Byte -> encodeToByteArray().map { (it.toInt() xor other.toInt()).toByte() }.toByteArray()
|
||||
this is Long && other is Long -> this xor other
|
||||
else -> throw UnsupportedOperationException("xor ${this?.javaClass} ^ ${other?.javaClass}")
|
||||
}
|
||||
|
||||
private fun Any?.join(): Any? = when (this) {
|
||||
is ByteArray -> decodeToString()
|
||||
is CharArray -> concatToString()
|
||||
is IntArray -> joinToString(",", "[", "]")
|
||||
is LongArray -> joinToString(",", "[", "]")
|
||||
is ShortArray -> joinToString(",", "[", "]")
|
||||
is FloatArray -> joinToString(",", "[", "]")
|
||||
is DoubleArray -> joinToString(",", "[", "]")
|
||||
is kotlin.Array<*> -> joinToString(",", "[", "]")
|
||||
is Iterable<*> -> joinToString(",", "[", "]")
|
||||
else -> this
|
||||
}
|
||||
|
||||
private fun String.deXor(): String = map { Char(it.code xor xorSecret.toInt()) }.toCharArray().concatToString()
|
||||
|
||||
private fun Any?.deXor(): Any? = when {
|
||||
this is RecaptchaWebCode.Arg && this.asObject() is String -> this.asObject()!!.deXor()
|
||||
this is String -> this.deXor()
|
||||
else -> this
|
||||
}
|
||||
|
||||
private fun Any.asClass(): Class<*>? = when (this) {
|
||||
is RecaptchaWebCode.Arg -> asObject()!!.asClass()
|
||||
is Int -> intToClassMap[this]!!
|
||||
is String -> getClass(this)
|
||||
is Class<*> -> this
|
||||
else -> throw UnsupportedOperationException("$this.asClass()")
|
||||
}
|
||||
|
||||
private fun Any?.getClass(): Class<*> = when (this) {
|
||||
is RecaptchaWebCode.Arg -> asObject().getClass()
|
||||
is Class<*> -> this
|
||||
null -> Unit.javaClass
|
||||
else -> this.javaClass
|
||||
}
|
||||
|
||||
private fun Any?.display(): String = when (this) {
|
||||
is RecaptchaWebCode.Arg -> asObject().display() + if (index != null) " (d[$index])" else ""
|
||||
is Int, is Boolean -> "${this}"
|
||||
is Byte -> "${this}b"
|
||||
is Short -> "${this}s"
|
||||
is Long -> "${this}l"
|
||||
is Double -> "${this}d"
|
||||
is Float -> "${this}f"
|
||||
is String -> if (any { !it.isLetterOrDigit() && it !in listOf('.', '=', '-', '_') }) "<string with complex chars>" else "\"${this}\""
|
||||
is Class<*> -> name
|
||||
is Constructor<*> -> "{new ${declaringClass.name}(${parameterTypes.joinToString { it.name }})}"
|
||||
is Method -> "{${declaringClass.name}.$name(${parameterTypes.joinToString { it.name }})}"
|
||||
is Field -> "{${declaringClass.name}.$name}"
|
||||
is IntArray -> joinToString(prefix = "[", postfix = "]")
|
||||
is ByteArray -> joinToString(prefix = "[", postfix = "]b")
|
||||
is ShortArray -> joinToString(prefix = "[", postfix = "]s")
|
||||
is LongArray -> joinToString(prefix = "[", postfix = "]l")
|
||||
is FloatArray -> joinToString(prefix = "[", postfix = "]f")
|
||||
is DoubleArray -> joinToString(prefix = "[", postfix = "]d")
|
||||
is BooleanArray -> joinToString(prefix = "[", postfix = "]")
|
||||
null -> "null"
|
||||
else -> "@{${this.javaClass.name}}"
|
||||
}
|
||||
|
||||
private fun RecaptchaWebCode.Arg.asObject(): Any? = when {
|
||||
index != null -> dict[index]
|
||||
bol != null -> bol
|
||||
bt != null -> bt[0]
|
||||
chr != null -> chr[0]
|
||||
sht != null -> sht.toShort()
|
||||
i != null -> i
|
||||
l != null -> l
|
||||
flt != null -> flt
|
||||
dbl != null -> dbl
|
||||
str != null -> str
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun Any.asListValue(): RecaptchaWebList.Value = when(this) {
|
||||
is Int -> RecaptchaWebList.Value(i = this)
|
||||
is Short -> RecaptchaWebList.Value(sht = this.toInt())
|
||||
is Byte -> RecaptchaWebList.Value(bt = ByteString.of(this))
|
||||
is Long -> RecaptchaWebList.Value(l = this)
|
||||
is Double -> RecaptchaWebList.Value(dbl = this)
|
||||
is Float -> RecaptchaWebList.Value(flt = this)
|
||||
is Boolean -> RecaptchaWebList.Value(bol = this)
|
||||
is Char -> RecaptchaWebList.Value(chr = this.toString())
|
||||
is String -> RecaptchaWebList.Value(str = this)
|
||||
else -> RecaptchaWebList.Value(str = toString())
|
||||
}
|
||||
|
||||
fun execute(code: RecaptchaWebCode) {
|
||||
for (op in code.ops) {
|
||||
when (op.code) {
|
||||
1 -> {
|
||||
// d[i] = a0
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${op.args[0].display()}")
|
||||
dict[op.arg1!!] = op.args[0].asObject()
|
||||
}
|
||||
|
||||
2 -> {
|
||||
// d[i] = a0 .. a1
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = \"${op.args[0].display()}${op.args[1].display()}\"")
|
||||
dict[op.arg1!!] = "${op.args[0].asObject()}${op.args[1].asObject()}"
|
||||
}
|
||||
|
||||
3 -> {
|
||||
// d[i] = Class(a0)
|
||||
val cls = op.args[0].asObject().deXor()?.asClass()
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = $cls")
|
||||
dict[op.arg1!!] = cls
|
||||
}
|
||||
|
||||
4 -> {
|
||||
// d[i] = Class(a0).getConstructor(a1 ...)
|
||||
val constructor = op.args[0].asClass()!!.getConstructor(*op.args.subList(1, op.args.size).map { it.asClass() }.toTypedArray())
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${constructor.display()}")
|
||||
dict[op.arg1!!] = constructor
|
||||
}
|
||||
|
||||
5 -> {
|
||||
// d[i] = Class(a0).getMethod(a1, a2 ...)
|
||||
val methodName = (op.args[1].asObject().deXor() as String)
|
||||
val cls = op.args[0].getClass()
|
||||
val method = getMethod(cls, methodName, op.args.subList(2, op.args.size).map { it.asClass() }.toTypedArray())
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${method.display()}")
|
||||
dict[op.arg1!!] = method
|
||||
}
|
||||
|
||||
6 -> {
|
||||
// d[i] = Class(a0).getField(a1)
|
||||
val fieldName = (op.args[1].asObject().deXor() as String)
|
||||
val cls = op.args[0].getClass()
|
||||
val field = getField(cls, fieldName)
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${field.display()}")
|
||||
dict[op.arg1!!] = field
|
||||
}
|
||||
|
||||
7 -> {
|
||||
// d[i] = Constructor(a0).newInstance(a1 ...)
|
||||
if (DEBUG) Log.d(
|
||||
TAG,
|
||||
"d[${op.arg1}] = new ${(op.args[0].asObject() as Constructor<*>).name}(${
|
||||
op.args.subList(1, op.args.size).joinToString { it.display() }
|
||||
})"
|
||||
)
|
||||
dict[op.arg1!!] =
|
||||
(op.args[0].asObject() as Constructor<*>).newInstance(*op.args.subList(1, op.args.size).map { it.asObject() }.toTypedArray())
|
||||
}
|
||||
|
||||
8 -> {
|
||||
// d[i] = Method(a0).invoke(a1, a2 ...)
|
||||
if (DEBUG) Log.d(
|
||||
TAG,
|
||||
"d[${op.arg1}] = (${op.args[1].display()}).${(op.args[0].asObject() as Method).name}(${
|
||||
op.args.subList(2, op.args.size).joinToString { it.display() }
|
||||
})"
|
||||
)
|
||||
dict[op.arg1!!] = (op.args[0].asObject() as Method).invoke(
|
||||
op.args[1].asObject(),
|
||||
*op.args.subList(2, op.args.size).map { it.asObject() }.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
9 -> {
|
||||
// d[i] = Method(a0).invoke(null, a1 ...)
|
||||
if (DEBUG) Log.d(
|
||||
TAG,
|
||||
"d[${op.arg1}] = ${(op.args[0].asObject() as Method).declaringClass.name}.${(op.args[0].asObject() as Method).name}(${
|
||||
op.args.subList(
|
||||
1,
|
||||
op.args.size
|
||||
).joinToString { it.display() }
|
||||
})"
|
||||
)
|
||||
dict[op.arg1!!] =
|
||||
(op.args[0].asObject() as Method).invoke(null, *op.args.subList(1, op.args.size).map { it.asObject() }.toTypedArray())
|
||||
}
|
||||
|
||||
10 -> {
|
||||
// d[i] = Field(a0).get(a1)
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = (${op.args[1].display()}).${(op.args[0].asObject() as Field).name}")
|
||||
dict[op.arg1!!] = (op.args[0].asObject() as Field).get(op.args[1].asObject())
|
||||
}
|
||||
|
||||
11 -> {
|
||||
// d[i] = Field(a0).get(null)
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${(op.args[0].asObject() as Field).declaringClass.name}.${(op.args[0].asObject() as Field).name}")
|
||||
dict[op.arg1!!] = (op.args[0].asObject() as Field).get(null)
|
||||
}
|
||||
|
||||
12 -> {
|
||||
// Field(a0).set(a1, a2)
|
||||
if (DEBUG) Log.d(TAG, "(${op.args[1].display()}).${(op.args[0].asObject() as Field).name} = ${op.args[2].display()}")
|
||||
(op.args[0].asObject() as Field).set(op.args[1].asObject(), op.args[2].asObject())
|
||||
}
|
||||
|
||||
13 -> {
|
||||
// Field(a0).set(null, a1)
|
||||
if (DEBUG) Log.d(
|
||||
TAG,
|
||||
"(${(op.args[0].asObject() as Field).declaringClass.name}).${(op.args[0].asObject() as Field).name} = ${op.args[1].display()}"
|
||||
)
|
||||
(op.args[0].asObject() as Field).set(null, op.args[1].asObject())
|
||||
}
|
||||
|
||||
15 -> {
|
||||
// eval(a0(a1))
|
||||
impl.eval("${op.args[0].str}(\"${op.args[1].asObject()}\")")
|
||||
}
|
||||
|
||||
17 -> {
|
||||
// d[i] = new a0[a1]
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = new ${op.args[0].asClass()!!.name}[${op.args[1].display()}]")
|
||||
dict[op.arg1!!] = Array.newInstance(op.args[0].asClass(), op.args[1].asObject() as Int)
|
||||
}
|
||||
|
||||
18 -> {
|
||||
// d[i] = new a1() { * a2(args) { eval(a0(args)); return a3; } }
|
||||
val callbackName = op.args[0].asObject() as String
|
||||
val methodName = (op.args[2].asObject() as String).deXor()
|
||||
val cls = op.args[1].asObject().deXor()?.asClass()
|
||||
val returnValue = op.args[3].asObject()
|
||||
val argsTarget = (if (op.args.size == 5) op.args[4].asObject() as? Int else null) ?: -1
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = new ${cls?.name}() { * ${methodName}(*) { js:$callbackName(*); return ${returnValue.display()}; } }")
|
||||
dict[op.arg1!!] =
|
||||
Proxy.newProxyInstance(cls!!.classLoader, arrayOf(cls)) { obj: Any, method: Method, args: kotlin.Array<Any>? ->
|
||||
if (method.name == methodName) {
|
||||
if (argsTarget != -1) dict[argsTarget] = args
|
||||
val encoded = RecaptchaWebList(args.orEmpty().map { it.asListValue() }).encode().toBase64(Base64.URL_SAFE, Base64.NO_WRAP)
|
||||
impl.eval("${callbackName}(\"$encoded\")")
|
||||
returnValue
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19 -> {
|
||||
// d[i] = new Queue(a1)
|
||||
// d[a0] = new a2() { * a3(args) { d[i].add(args); return a4; } }
|
||||
val methodName = (op.args[3].asObject() as String).deXor()
|
||||
val maxSize = op.args[1].asObject() as Int
|
||||
val queue = ArrayDeque<List<Any>>(maxSize)
|
||||
val limitedQueue = object : Queue<List<Any>> by queue {
|
||||
override fun add(element: List<Any>?): Boolean {
|
||||
if (maxSize == 0) return true
|
||||
if (size == maxSize) remove()
|
||||
queue.add(element)
|
||||
return true
|
||||
}
|
||||
}
|
||||
val returnValue = if (op.args.size == 5) op.args[4].asObject() else null
|
||||
val cls = op.args[2].asObject().deXor()?.asClass()
|
||||
dict[op.arg1!!] = limitedQueue
|
||||
dict[op.args[0].asObject() as Int] = Proxy.newProxyInstance(cls!!.classLoader, arrayOf(cls)) { obj: Any, method: Method, args: kotlin.Array<Any>? ->
|
||||
if (method.name == methodName) {
|
||||
limitedQueue.add(args?.asList().orEmpty())
|
||||
returnValue
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20 -> {
|
||||
// unset(d, a0 ...)
|
||||
if (DEBUG) Log.d(TAG, "d[${op.args.joinToString { it.index.toString() }}] = @@@")
|
||||
for (arg in op.args) {
|
||||
dict.remove(arg.index)
|
||||
}
|
||||
}
|
||||
|
||||
26 -> {
|
||||
// e = a0
|
||||
errorHandler = op.args[0].str!!
|
||||
}
|
||||
|
||||
27 -> {
|
||||
// clear(d)
|
||||
dict.clear()
|
||||
}
|
||||
|
||||
30 -> {
|
||||
// d[i] = encode(a0 ...)
|
||||
val res = RecaptchaWebList(op.args.map { it.asObject()!!.asListValue() }).encode().toBase64(Base64.URL_SAFE, Base64.NO_WRAP)
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${res.display()}")
|
||||
dict[op.arg1!!] = res
|
||||
}
|
||||
|
||||
31 -> {
|
||||
// a0[a1] = a2
|
||||
if (DEBUG) Log.d(TAG, "d[${op.args[0].index}][${op.args[1].display()}] = ${op.args[2].display()}")
|
||||
Array.set(op.args[0].asObject()!!, op.args[1].asObject() as Int, op.args[2].asObject())
|
||||
}
|
||||
|
||||
32 -> {
|
||||
// d[i] = a0[a1]
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${op.args[0].display()}[${op.args[1].display()}]")
|
||||
val arr = op.args[0].asObject()
|
||||
val idx = op.args[1].asObject() as Int
|
||||
val res = when (arr) {
|
||||
is String -> arr[idx]
|
||||
is List<*> -> arr[idx]
|
||||
else -> Array.get(arr, idx)
|
||||
}
|
||||
dict[op.arg1!!] = res
|
||||
}
|
||||
|
||||
34 -> {
|
||||
// d[i] = a0 % a1
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${op.args[0].display()} % ${op.args[1].display()}")
|
||||
dict[op.arg1!!] = op.args[0].asObject() % op.args[1].asObject()
|
||||
}
|
||||
|
||||
35 -> {
|
||||
// d[i] = a0 ^ a1
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${op.args[0].display()} ^ ${op.args[1].display()}")
|
||||
dict[op.arg1!!] = op.args[0].asObject() xor op.args[1].asObject()
|
||||
}
|
||||
|
||||
37 -> {
|
||||
// d[i] = String(a1[*a0])
|
||||
val str = op.args[1].asObject() as String
|
||||
val res = (op.args[0].asObject() as IntArray).map { str[it] }.toCharArray().concatToString()
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${res.display()}")
|
||||
dict[op.arg1!!] = res
|
||||
}
|
||||
|
||||
38 -> {
|
||||
// x = a0
|
||||
xorSecret = op.args[0].asObject() as Byte
|
||||
}
|
||||
|
||||
39 -> {
|
||||
// d[i] = join(a0)
|
||||
val res = op.args[0].asObject().join()
|
||||
if (DEBUG) Log.d(TAG, "d[${op.arg1}] = ${res.display()}")
|
||||
dict[op.arg1!!] = res
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Op ${op.encode().toBase64(Base64.URL_SAFE, Base64.NO_WRAP)} not implemented (code=${op.code})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RNJavaScriptInterface(private val impl: RecaptchaWebImpl, private val interpreter: CodeInterpreter) {
|
||||
|
||||
@JavascriptInterface
|
||||
fun zzoed(input: String) {
|
||||
val result = RecaptchaWebResult.ADAPTER.decode(Base64.decode(input, Base64.URL_SAFE))
|
||||
if (DEBUG) Log.d(TAG, "zzoed: $result")
|
||||
if (!impl.executeFinished.getAndSet(true) && impl.lastRequestToken == result.requestToken) {
|
||||
if (result.code == 1 && result.token != null) {
|
||||
impl.executeContinuation?.resume(result.token)
|
||||
} else {
|
||||
impl.executeContinuation?.resumeWithException(RuntimeException("Status ${result.code}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun zzoid(input: String) {
|
||||
val status = RecaptchaWebStatusCode.ADAPTER.decode(Base64.decode(input, Base64.URL_SAFE))
|
||||
if (DEBUG) Log.d(TAG, "zzoid: $status")
|
||||
if (!impl.initFinished.getAndSet(true)) {
|
||||
if (status.code != null) {
|
||||
impl.initContinuation?.resume(status.code)
|
||||
} else {
|
||||
impl.initContinuation?.resumeWithException(RuntimeException("Status is null"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun zzrp(input: String) {
|
||||
val callback = RecaptchaWebEncryptedCallback.ADAPTER.decode(Base64.decode(input, Base64.URL_SAFE))
|
||||
var key = (codeDecryptKeyPrefix + callback.key).reduce { a, b -> a xor b }
|
||||
fun next(): Int {
|
||||
key = ((key * 4391) + 277) % 32779
|
||||
return key % 255
|
||||
}
|
||||
|
||||
val decrypted = callback.data_?.map { Char(it.code xor next()) }?.toCharArray()?.concatToString()
|
||||
if (DEBUG) Log.d(TAG, "zzrp: $decrypted")
|
||||
val code = RecaptchaWebCode.ADAPTER.decode(Base64.decode(decrypted, Base64.URL_SAFE + Base64.NO_PADDING))
|
||||
interpreter.execute(code)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
90
play-services-recaptcha/core/src/main/proto/recaptcha.proto
Normal file
90
play-services-recaptcha/core/src/main/proto/recaptcha.proto
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option java_package = "org.microg.gms.recaptcha";
|
||||
|
||||
message RecaptchaInitRequest {
|
||||
message Data {
|
||||
optional string siteKey = 1;
|
||||
optional string packageName = 2;
|
||||
optional string version = 3;
|
||||
}
|
||||
optional Data data = 2;
|
||||
}
|
||||
|
||||
message RecaptchaInitResponse {
|
||||
optional string token = 1;
|
||||
repeated string acceptableAdditionalArgs = 3;
|
||||
}
|
||||
|
||||
message RecaptchaExecuteRequest {
|
||||
optional string token = 1;
|
||||
optional string action = 2;
|
||||
optional uint64 timestamp = 3;
|
||||
optional string dg = 4;
|
||||
map<string, string> additionalArgs = 5;
|
||||
optional string verificationHistoryToken = 6;
|
||||
// map<int32, bytes> unknown = 7;
|
||||
}
|
||||
|
||||
message RecaptchaExecuteResponse {
|
||||
optional string token = 1;
|
||||
}
|
||||
|
||||
message RecaptchaWebEncryptedCallback {
|
||||
optional string data = 1;
|
||||
repeated int32 key = 2;
|
||||
}
|
||||
|
||||
message RecaptchaWebInvokeMultiParameter {
|
||||
repeated string args = 1;
|
||||
}
|
||||
|
||||
message RecaptchaWebStatusCode {
|
||||
optional int32 code = 1;
|
||||
}
|
||||
|
||||
message RecaptchaWebResult {
|
||||
optional string requestToken = 1;
|
||||
optional string token = 2;
|
||||
optional int32 code = 3;
|
||||
}
|
||||
|
||||
message RecaptchaWebList {
|
||||
message Value {
|
||||
oneof typed {
|
||||
bool bol = 1;
|
||||
bytes bt = 2;
|
||||
string chr = 3;
|
||||
sint32 sht = 4;
|
||||
sint32 i = 5;
|
||||
sint64 l = 7;
|
||||
float flt = 9;
|
||||
double dbl = 10;
|
||||
string str = 11;
|
||||
}
|
||||
}
|
||||
repeated Value values = 1;
|
||||
}
|
||||
|
||||
message RecaptchaWebCode {
|
||||
message Arg {
|
||||
oneof typed {
|
||||
int32 index = 1;
|
||||
bool bol = 2;
|
||||
bytes bt = 3;
|
||||
string chr = 4;
|
||||
sint32 sht = 5;
|
||||
sint32 i = 6;
|
||||
sint64 l = 8;
|
||||
float flt = 10;
|
||||
double dbl = 11;
|
||||
string str = 12;
|
||||
}
|
||||
}
|
||||
message Op {
|
||||
optional int32 code = 1;
|
||||
optional int32 arg1 = 2;
|
||||
repeated Arg args = 3;
|
||||
}
|
||||
repeated Op ops = 1;
|
||||
}
|
||||
7
play-services-recaptcha/src/main/AndroidManifest.xml
Normal file
7
play-services-recaptcha/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2022, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest />
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha;
|
||||
|
||||
parcelable RecaptchaAction;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha;
|
||||
|
||||
parcelable RecaptchaHandle;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha;
|
||||
|
||||
parcelable RecaptchaResultData;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
parcelable ExecuteParams;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
parcelable ExecuteResults;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
interface ICloseCallback {
|
||||
oneway void onClosed(in Status status, boolean closed) = 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaResultData;
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteResults;
|
||||
|
||||
interface IExecuteCallback {
|
||||
oneway void onData(in Status status, in RecaptchaResultData data) = 0;
|
||||
oneway void onResults(in Status status, in ExecuteResults results) = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle;
|
||||
import com.google.android.gms.recaptcha.internal.InitResults;
|
||||
|
||||
interface IInitCallback {
|
||||
oneway void onHandle(in Status status, in RecaptchaHandle handle) = 0;
|
||||
oneway void onResults(in Status status, in InitResults results) = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaAction;
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle;
|
||||
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteParams;
|
||||
import com.google.android.gms.recaptcha.internal.InitParams;
|
||||
|
||||
import com.google.android.gms.recaptcha.internal.ICloseCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IExecuteCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IInitCallback;
|
||||
|
||||
interface IRecaptchaService {
|
||||
void verifyWithRecaptcha(IExecuteCallback callback, String siteKey, String packageName) = 0;
|
||||
void init(IInitCallback callback, String siteKey) = 1;
|
||||
void execute(IExecuteCallback callback, in RecaptchaHandle handle, in RecaptchaAction action) = 2;
|
||||
void close(ICloseCallback callback, in RecaptchaHandle handle) = 3;
|
||||
void init2(IInitCallback callback, in InitParams params) = 4;
|
||||
void execute2(IExecuteCallback callback, in ExecuteParams params) = 5;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
parcelable InitParams;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
parcelable InitResults;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
/**
|
||||
* Exception thrown when the server returns a non-200 response code.
|
||||
*/
|
||||
public class HttpStatusException extends Exception {
|
||||
private int errorHttpStatus;
|
||||
|
||||
/**
|
||||
* Constructs a {@link HttpStatusException} with the specified detail message and error code.
|
||||
*
|
||||
* @param msg The detail message (which is saved for later retrieval by the {@link #getMessage()} method)
|
||||
* @param errorHttpStatus The status code of the failed HTTP request.
|
||||
*/
|
||||
public HttpStatusException(String msg, int errorHttpStatus) {
|
||||
super(msg);
|
||||
this.errorHttpStatus = errorHttpStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status code of a failed HTTP request.
|
||||
*/
|
||||
public int getHttpErrorStatus() {
|
||||
return errorHttpStatus;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import org.microg.gms.recaptcha.RecaptchaClientImpl;
|
||||
|
||||
/**
|
||||
* The reCAPTCHA API provides access to Google Cloud services that help you protect your app from spam and other
|
||||
* abusive actions.
|
||||
* <p>
|
||||
* To instantiate a reCAPTCHA mobile client, call {@link #getClient(Context)} or {@link #getClient(Activity)}.
|
||||
*/
|
||||
public class Recaptcha {
|
||||
|
||||
/**
|
||||
* Returns a {@link RecaptchaClient} that is used to access all APIs that are called when the app has a foreground
|
||||
* {@link Activity}.
|
||||
* <p>
|
||||
* Use this method over {@link #getClient(Context)} to improve performance if you plan to make multiple API calls
|
||||
* from your application's foreground {@link Activity}.
|
||||
*/
|
||||
public static RecaptchaClient getClient(Activity activity) {
|
||||
return new RecaptchaClientImpl(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RecaptchaClient} that is used to access all APIs that are called without access to a foreground
|
||||
* {@link Activity}.
|
||||
*/
|
||||
public static RecaptchaClient getClient(Context context) {
|
||||
return new RecaptchaClientImpl(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
/**
|
||||
* Actions (e.g., login) intended to be protected by reCAPTCHA. An instance of this object should be passed to
|
||||
* {@link RecaptchaClient#execute(RecaptchaHandle, RecaptchaAction)}.
|
||||
*/
|
||||
public class RecaptchaAction extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
private RecaptchaActionType action;
|
||||
@Field(2)
|
||||
private String customAction;
|
||||
@Field(3)
|
||||
private Bundle additionalArgs;
|
||||
@Field(4)
|
||||
private String verificationHistoryToken;
|
||||
|
||||
private RecaptchaAction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RecaptchaAction} instance with a predefined reCAPTCHA action.
|
||||
*/
|
||||
public RecaptchaAction(RecaptchaActionType action) {
|
||||
this(action, Bundle.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RecaptchaAction} instance with a predefined reCAPTCHA action and additional arguments.
|
||||
*/
|
||||
public RecaptchaAction(RecaptchaActionType action, Bundle additionalArgs) {
|
||||
this.action = action;
|
||||
this.additionalArgs = additionalArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RecaptchaAction} instance with a custom action String.
|
||||
*/
|
||||
public RecaptchaAction(String customAction) {
|
||||
this(customAction, Bundle.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RecaptchaAction} instance with a custom action in the form of a String and additional arguments.
|
||||
*/
|
||||
public RecaptchaAction(String customAction, Bundle additionalArgs) {
|
||||
this.action = new RecaptchaActionType(RecaptchaActionType.OTHER);
|
||||
this.customAction = customAction;
|
||||
this.additionalArgs = additionalArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets {@code RecaptchaActionType}.
|
||||
*/
|
||||
public RecaptchaActionType getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional arg map specified by this action.
|
||||
*/
|
||||
public Bundle getAdditionalArgs() {
|
||||
return additionalArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets custom action that user inputs.
|
||||
*/
|
||||
public String getCustomAction() {
|
||||
return customAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the verification history token specified by this action.
|
||||
*/
|
||||
public String getVerificationHistoryToken() {
|
||||
return verificationHistoryToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the String value of {@code RecaptchaAction}.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
if (RecaptchaActionType.OTHER.equals(action.name) && customAction != null) {
|
||||
return customAction;
|
||||
} else {
|
||||
return action.name;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<RecaptchaAction> CREATOR = new AutoCreator<>(RecaptchaAction.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
/**
|
||||
* Collection of predefined actions used by RecaptchaHandle.
|
||||
*/
|
||||
public class RecaptchaActionType extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
String name;
|
||||
|
||||
private RecaptchaActionType() {}
|
||||
|
||||
public RecaptchaActionType(String action) {
|
||||
this.name = action;
|
||||
}
|
||||
|
||||
public static final Creator<RecaptchaActionType> CREATOR = new AutoCreator<>(RecaptchaActionType.class);
|
||||
|
||||
/**
|
||||
* User interaction that needs to be verified while the user is performing the workflow you would like to protect.
|
||||
*/
|
||||
public @interface Action {}
|
||||
|
||||
/**
|
||||
* Indicates that the protected action is a login workflow.
|
||||
*/
|
||||
public static final String LOGIN = "login";
|
||||
/**
|
||||
* When a custom action is specified, reCAPTCHA uses this value automatically.
|
||||
*/
|
||||
public static final String OTHER = "other";
|
||||
/**
|
||||
* Indicates that the protected action is a signup workflow.
|
||||
*/
|
||||
public static final String SIGNUP = "signup";
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
|
||||
/**
|
||||
* The main entry point for interacting with the reCAPTCHA API.
|
||||
*/
|
||||
public interface RecaptchaClient {
|
||||
/**
|
||||
* Sends a challenge to an account in order to verify the identity of the user.
|
||||
* <p>
|
||||
* This method can be optionally called if you decide to perform a two factor authentication check on an account.
|
||||
*
|
||||
* @param recaptchaHandle RecaptchaHandle initialized through {@link #init(String)}.
|
||||
* @param challengeRequestToken The challenge request token obtained through CreateAssessment().
|
||||
* @return A VerificationHandle that can be used with {@link #verifyAccount(String, VerificationHandle)} calls. A
|
||||
* handle is usually valid for a specific time after creation. If an expired handle is returned, meaning the
|
||||
* operation was aborted, {@link VerificationHandle#getOperationAbortedToken()} will return a token that can be
|
||||
* used with the reCAPTCHA Enterprise API CreateAssessment() to get more details.
|
||||
*/
|
||||
Task<VerificationHandle> challengeAccount(RecaptchaHandle recaptchaHandle, String challengeRequestToken);
|
||||
|
||||
/**
|
||||
* Closes the initialized RecaptchaHandle.
|
||||
* <p>
|
||||
* Closes the handle if you will not be using it again to save resources.
|
||||
*
|
||||
* @param handle RecaptchaHandle initialized through {@link #init(String)}.
|
||||
* @return true if the handle got closed successfully for the first time, false if there wasn't anything to close
|
||||
* (e.g., the handle was already previously closed).
|
||||
*/
|
||||
Task<Boolean> close(RecaptchaHandle handle);
|
||||
|
||||
/**
|
||||
* Returns a score indicating how likely the action was triggered by a real user. A score close to 0 indicates a likely bot, and a score close to 1 indicates a likely human.
|
||||
* <p>
|
||||
* This method should be called every time there is an action to be protected.
|
||||
*
|
||||
* @param handle {@link RecaptchaHandle} initialized through {@link #init(String)}.
|
||||
* @param action User interaction that needs to be protected.
|
||||
*/
|
||||
Task<RecaptchaResultData> execute(RecaptchaHandle handle, RecaptchaAction action);
|
||||
|
||||
/**
|
||||
* Prepares and initializes a RecaptchaHandle.
|
||||
*
|
||||
* @param siteKey A site key registered for this app
|
||||
*/
|
||||
Task<RecaptchaHandle> init(String siteKey);
|
||||
|
||||
/**
|
||||
* Verifies a PIN against a verification handle obtained through a
|
||||
* {@link #challengeAccount(RecaptchaHandle, String)} call.
|
||||
* <p>
|
||||
* The method should be called to verify a PIN submitted by the user. The returned {@link VerificationResult} will
|
||||
* contain a Status and either a {@link VerificationHandle} or a new reCAPTCHA token.
|
||||
*
|
||||
* @param pin The fixed-length numerical PIN entered by the user. If a PIN with unexpected length or
|
||||
* non numerical characters is entered, a
|
||||
* {@link RecaptchaStatusCodes#RECAPTCHA_2FA_INVALID_PIN} error will be returned.
|
||||
* @param verificationHandle A verification handle containing information required to preform the verification
|
||||
* operation.
|
||||
* @return A {@link VerificationResult} containing a {@link Status} indicating the status of the of the verification.
|
||||
*/
|
||||
Task<VerificationResult> verifyAccount(String pin, VerificationHandle verificationHandle);
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Information pertaining to reCAPTCHA handle, which is used to identify the initialized reCAPTCHA service.
|
||||
*/
|
||||
public class RecaptchaHandle extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
private String siteKey;
|
||||
@Field(2)
|
||||
private String clientPackageName;
|
||||
@Field(3)
|
||||
private List<String> acceptableAdditionalArgs;
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
private RecaptchaHandle() {
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public RecaptchaHandle(String siteKey, String clientPackageName, List<String> acceptableAdditionalArgs) {
|
||||
this.siteKey = siteKey;
|
||||
this.clientPackageName = clientPackageName;
|
||||
this.acceptableAdditionalArgs = acceptableAdditionalArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of strings indicating the additional argument keys that reCAPTCHA server accepts.
|
||||
*/
|
||||
public List<String> getAcceptableAdditionalArgs() {
|
||||
return Collections.unmodifiableList(acceptableAdditionalArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name of the app that calls reCAPTCHA API.
|
||||
*/
|
||||
public String getClientPackageName() {
|
||||
return clientPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reCAPTCHA Site Key you registered to help protect your application.
|
||||
*/
|
||||
public String getSiteKey() {
|
||||
return siteKey;
|
||||
}
|
||||
|
||||
public static final Creator<RecaptchaHandle> CREATOR = new AutoCreator<>(RecaptchaHandle.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception thrown when the mobile client fails to connect to the reCAPTCHA server.
|
||||
*/
|
||||
public class RecaptchaNetworkException extends Exception {
|
||||
/**
|
||||
* Constructs a {@link RecaptchaNetworkException} with the specified detail message and {@link IOException}.
|
||||
*
|
||||
* @param msg The detail message (which is saved for later retrieval by the {@link #getMessage()} method)
|
||||
* @param e The root {@link IOException} that causes the {@link RecaptchaNetworkException}.
|
||||
*/
|
||||
public RecaptchaNetworkException(String msg, IOException e) {
|
||||
super(msg, e);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
/**
|
||||
* Class for wrapping an optional object (i.e., an immutable object that may contain a non-null reference to another
|
||||
* object) to be used in {@link VerificationResult}.
|
||||
*/
|
||||
public class RecaptchaOptionalObject<T> {
|
||||
private T object;
|
||||
|
||||
private RecaptchaOptionalObject(T object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RecaptchaOptionalObject} wrapping the specified object, which can be {@code null}.
|
||||
*/
|
||||
public static <T> RecaptchaOptionalObject<T> ofNullable(T object) {
|
||||
return new RecaptchaOptionalObject<>(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped object.
|
||||
*/
|
||||
public T orNull() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
/**
|
||||
* Information pertaining to reCAPTCHA result data.
|
||||
*/
|
||||
public class RecaptchaResultData extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
private String tokenResult;
|
||||
|
||||
private RecaptchaResultData() {
|
||||
}
|
||||
|
||||
public RecaptchaResultData(String token) {
|
||||
this.tokenResult = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reCAPTCHA token.
|
||||
*/
|
||||
public String getTokenResult() {
|
||||
return tokenResult;
|
||||
}
|
||||
|
||||
public static final Creator<RecaptchaResultData> CREATOR = new AutoCreator<>(RecaptchaResultData.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
|
||||
/**
|
||||
* Status codes for the reCAPTCHA API.
|
||||
*/
|
||||
public final class RecaptchaStatusCodes extends CommonStatusCodes {
|
||||
/**
|
||||
* reCAPTCHA feature is disabled.
|
||||
* <p>
|
||||
* Please check if you update Google Play Services on your phone to get our latest reCAPTCHA module code that
|
||||
* matches to the API versions you used in the SDK in your app.
|
||||
*/
|
||||
public static final int RECAPTCHA_FEATURE_OFF = 36004;
|
||||
/**
|
||||
* An internal error occurred during two factor authentication calls.
|
||||
* <p>
|
||||
* Please try again in a bit.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_UNKNOWN = 36005;
|
||||
/**
|
||||
* The challenge account request token has expired.
|
||||
* <p>
|
||||
* Please obtain another request token from the reCAPTCHA Enterprise server.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_CHALLENGE_EXPIRED = 36006;
|
||||
/**
|
||||
* The challenge account request token is invalid.
|
||||
* <p>
|
||||
* Please verify that you are using the correct token obtained from the reCAPTCHA Enterprise server.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_INVALID_REQUEST_TOKEN = 36007;
|
||||
/**
|
||||
* The verification PIN has invalid format.
|
||||
* <p>
|
||||
* Please verify that the input PIN is of the right length and only contain numerical characters.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_INVALID_PIN = 36008;
|
||||
/**
|
||||
* The verification PIN does not match the PIN sent to the challenged account.
|
||||
* <p>
|
||||
* Please try again using a new {@link VerificationHandle}.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_PIN_MISMATCH = 36009;
|
||||
/**
|
||||
* All allowed verification attempts are exhausted.
|
||||
* <p>
|
||||
* Please restart the verification workflow by calling
|
||||
* {@link RecaptchaClient#execute(RecaptchaHandle, RecaptchaAction)} again to fetch a new reCAPTCHA token, for
|
||||
* retrieving a new challenge token via reCAPTCHA Enterprise API CreateAssessment(), then calling
|
||||
* {@link RecaptchaClient#challengeAccount(RecaptchaHandle, String)}.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_ATTEMPTS_EXHAUSTED = 36010;
|
||||
/**
|
||||
* The operation was aborted.
|
||||
* <p>
|
||||
* Please use the abortion token with the reCAPTCHA Enterprise server to obtain more information.
|
||||
*/
|
||||
public static final int RECAPTCHA_2FA_ABORTED = 36014;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import com.google.android.gms.common.internal.safeparcel.SafeParcelable;
|
||||
|
||||
/**
|
||||
* Stores the information required to verify an account.
|
||||
* <p>
|
||||
* This object is only valid for a specific time after creation and it holds all the information needed to validate a
|
||||
* PIN using {@link RecaptchaClient#verifyAccount(String, VerificationHandle)}. If an expired handle is returned by
|
||||
* {@link RecaptchaClient#challengeAccount(RecaptchaHandle, String)}, then {@link #getOperationAbortedToken()} will
|
||||
* return a token that can be used with the reCAPTCHA Enterprise API CreateAssessment() to get more details.
|
||||
*/
|
||||
public abstract class VerificationHandle implements SafeParcelable {
|
||||
/**
|
||||
* Returns the length of the PIN code.
|
||||
*/
|
||||
public abstract int getCodeLength();
|
||||
|
||||
/**
|
||||
* Returns a reCAPTCHA token in the case {@link RecaptchaClient#challengeAccount(RecaptchaHandle, String)}
|
||||
* operation was aborted and an expired {@link VerificationHandle} was returned, otherwise this returns an empty
|
||||
* string. This can be used with the reCAPTCHA Enterprise API CreateAssessment() to retrieve more details.
|
||||
*/
|
||||
public abstract String getOperationAbortedToken();
|
||||
|
||||
/**
|
||||
* Returns the site public key you registered for using reCAPTCHA.
|
||||
*/
|
||||
public abstract String getSiteKey();
|
||||
|
||||
/**
|
||||
* Returns the validity duration of the object since its creation in minutes.
|
||||
*/
|
||||
public abstract long getTimeoutMinutes();
|
||||
|
||||
/**
|
||||
* Returns an encrypted version of the internal verification token that will be used in
|
||||
* {@link RecaptchaClient#verifyAccount(String, VerificationHandle)} call.
|
||||
*/
|
||||
public abstract String getVerificationToken();
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating if the {@link VerificationHandle} is valid for
|
||||
* {@link RecaptchaClient#verifyAccount(String, VerificationHandle)} API calls. An invalid handle will cause
|
||||
* {@link RecaptchaClient#verifyAccount(String, VerificationHandle)} calls to fail immediately.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Notice: Portions of this file are reproduced from work created and shared by Google and used
|
||||
* according to terms described in the Creative Commons 4.0 Attribution License.
|
||||
* See https://developers.google.com/readme/policies for details.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha;
|
||||
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
/**
|
||||
* Immmutable object to hold the result of a verification operation.
|
||||
*/
|
||||
public abstract class VerificationResult {
|
||||
/**
|
||||
* Returns a status to provide more info on the result of the verification operation.
|
||||
*
|
||||
* @return {@link CommonStatusCodes#SUCCESS} or {@link RecaptchaStatusCodes#RECAPTCHA_2FA_ABORTED} status if the
|
||||
* verification operation succeeded, and non-success status (e.g.
|
||||
* {@link RecaptchaStatusCodes#RECAPTCHA_2FA_PIN_MISMATCH}) if the verification failed with more attempts
|
||||
* available, i.e. user entered wrong pin.
|
||||
*/
|
||||
public abstract Status getVerificationStatus();
|
||||
|
||||
/**
|
||||
* Returns an optional containing the reCAPTCHA token if the verification operation was successful or aborted.
|
||||
*
|
||||
* @return the reCAPTCHA token if {@code getVerificationStatus().equals(CommonStatusCodes.SUCCESS)} or
|
||||
* {@code getVerificationStatus().equals RecaptchaStatusCodes.RECAPTCHA_2FA_ABORTED)}, otherwise an empty
|
||||
* {@link RecaptchaOptionalObject}.
|
||||
*/
|
||||
public abstract RecaptchaOptionalObject<String> recaptchaToken();
|
||||
|
||||
/**
|
||||
* Returns an optional containing a verification handle if the verification operation failed and the client is allowed to retry.
|
||||
*
|
||||
* @return a verification handle on a failure status, otherwise an empty {@link RecaptchaOptionalObject}.
|
||||
*/
|
||||
public abstract RecaptchaOptionalObject<VerificationHandle> verificationHandle();
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaAction;
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle;
|
||||
|
||||
import org.microg.gms.utils.ToStringHelper;
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class ExecuteParams extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public RecaptchaHandle handle;
|
||||
@Field(2)
|
||||
public RecaptchaAction action;
|
||||
@Field(3)
|
||||
public String version;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringHelper.name("ExecuteParams")
|
||||
.field("handle", handle)
|
||||
.field("action", action)
|
||||
.field("version", version)
|
||||
.end();
|
||||
}
|
||||
|
||||
public static final Creator<ExecuteParams> CREATOR = new AutoCreator<>(ExecuteParams.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaResultData;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class ExecuteResults extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public RecaptchaResultData data;
|
||||
|
||||
public static final Creator<ExecuteResults> CREATOR = new AutoCreator<>(ExecuteResults.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.microg.gms.utils.ToStringHelper;
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class InitParams extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public String siteKey;
|
||||
@Field(2)
|
||||
public String version;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringHelper.name("InitParams")
|
||||
.field("siteKey", siteKey)
|
||||
.field("version", version)
|
||||
.end();
|
||||
}
|
||||
|
||||
public static final Creator<InitParams> CREATOR = new AutoCreator<>(InitParams.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.recaptcha.internal;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class InitResults extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public RecaptchaHandle handle;
|
||||
|
||||
public static final Creator<InitResults> CREATOR = new AutoCreator<>(InitResults.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.recaptcha;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.util.Log;
|
||||
import com.google.android.gms.common.api.Api;
|
||||
import com.google.android.gms.common.api.GoogleApi;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.recaptcha.Recaptcha;
|
||||
import com.google.android.gms.recaptcha.RecaptchaAction;
|
||||
import com.google.android.gms.recaptcha.RecaptchaClient;
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle;
|
||||
import com.google.android.gms.recaptcha.RecaptchaResultData;
|
||||
import com.google.android.gms.recaptcha.VerificationHandle;
|
||||
import com.google.android.gms.recaptcha.VerificationResult;
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteParams;
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteResults;
|
||||
import com.google.android.gms.recaptcha.internal.ICloseCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IExecuteCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IInitCallback;
|
||||
import com.google.android.gms.recaptcha.internal.InitParams;
|
||||
import com.google.android.gms.recaptcha.internal.InitResults;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.android.gms.tasks.Tasks;
|
||||
|
||||
import org.microg.gms.common.api.PendingGoogleApiCall;
|
||||
import org.microg.gms.tasks.TaskImpl;
|
||||
|
||||
public class RecaptchaClientImpl extends GoogleApi<Api.ApiOptions.NoOptions> implements RecaptchaClient {
|
||||
private int openHandles = 0;
|
||||
|
||||
public RecaptchaClientImpl(Context context) {
|
||||
super(context, new Api<>((options, c, looper, clientSettings, callbacks, connectionFailedListener) -> new RecaptchaGmsClient(c, callbacks, connectionFailedListener)), Api.ApiOptions.NO_OPTIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<VerificationHandle> challengeAccount(RecaptchaHandle recaptchaHandle, String challengeRequestToken) {
|
||||
return Tasks.forException(new UnsupportedOperationException());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Boolean> close(RecaptchaHandle handle) {
|
||||
return scheduleTask((PendingGoogleApiCall<Boolean, RecaptchaGmsClient>) (client, completionSource) -> {
|
||||
client.close(new ICloseCallback.Stub() {
|
||||
@Override
|
||||
public void onClosed(Status status, boolean closed) throws RemoteException {
|
||||
if (status.isSuccess()) {
|
||||
completionSource.trySetResult(closed);
|
||||
} else {
|
||||
completionSource.trySetException(new RuntimeException(status.getStatusMessage()));
|
||||
}
|
||||
if (openHandles == 0) {
|
||||
Log.w("RecaptchaClient", "Can't mark handle closed if none is open");
|
||||
return;
|
||||
}
|
||||
openHandles--;
|
||||
if (openHandles == 0) client.disconnect();
|
||||
}
|
||||
}, handle);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<RecaptchaResultData> execute(RecaptchaHandle handle, RecaptchaAction action) {
|
||||
return scheduleTask((PendingGoogleApiCall<RecaptchaResultData, RecaptchaGmsClient>) (client, completionSource) -> {
|
||||
ExecuteParams params = new ExecuteParams();
|
||||
params.handle = handle;
|
||||
params.action = action;
|
||||
params.version = "18.1.1";
|
||||
client.execute(new IExecuteCallback.Stub() {
|
||||
@Override
|
||||
public void onData(Status status, RecaptchaResultData data) throws RemoteException {
|
||||
if (status.isSuccess()) {
|
||||
completionSource.trySetResult(data);
|
||||
} else {
|
||||
completionSource.trySetException(new RuntimeException(status.getStatusMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResults(Status status, ExecuteResults results) throws RemoteException {
|
||||
if (status.isSuccess()) {
|
||||
completionSource.trySetResult(results.data);
|
||||
} else {
|
||||
completionSource.trySetException(new RuntimeException(status.getStatusMessage()));
|
||||
}
|
||||
}
|
||||
}, params);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<RecaptchaHandle> init(String siteKey) {
|
||||
openHandles++;
|
||||
return scheduleTask((PendingGoogleApiCall<RecaptchaHandle, RecaptchaGmsClient>) (client, completionSource) -> {
|
||||
InitParams params = new InitParams();
|
||||
params.siteKey = siteKey;
|
||||
params.version = "18.1.1";
|
||||
client.init(new IInitCallback.Stub() {
|
||||
@Override
|
||||
public void onHandle(Status status, RecaptchaHandle handle) throws RemoteException {
|
||||
if (status.isSuccess()) {
|
||||
completionSource.trySetResult(handle);
|
||||
} else {
|
||||
completionSource.trySetException(new RuntimeException(status.getStatusMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResults(Status status, InitResults results) throws RemoteException {
|
||||
if (status.isSuccess()) {
|
||||
completionSource.trySetResult(results.handle);
|
||||
} else {
|
||||
completionSource.trySetException(new RuntimeException(status.getStatusMessage()));
|
||||
}
|
||||
}
|
||||
}, params);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<VerificationResult> verifyAccount(String pin, VerificationHandle verificationHandle) {
|
||||
return Tasks.forException(new UnsupportedOperationException());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.recaptcha;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import org.microg.gms.common.GmsClient;
|
||||
import org.microg.gms.common.GmsService;
|
||||
import com.google.android.gms.common.api.internal.ConnectionCallbacks;
|
||||
import com.google.android.gms.common.api.internal.OnConnectionFailedListener;
|
||||
|
||||
import com.google.android.gms.recaptcha.RecaptchaAction;
|
||||
import com.google.android.gms.recaptcha.RecaptchaHandle;
|
||||
import com.google.android.gms.recaptcha.internal.ExecuteParams;
|
||||
import com.google.android.gms.recaptcha.internal.ICloseCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IExecuteCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IInitCallback;
|
||||
import com.google.android.gms.recaptcha.internal.IRecaptchaService;
|
||||
import com.google.android.gms.recaptcha.internal.InitParams;
|
||||
|
||||
public class RecaptchaGmsClient extends GmsClient<IRecaptchaService> {
|
||||
public RecaptchaGmsClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) {
|
||||
super(context, callbacks, connectionFailedListener, GmsService.RECAPTCHA.ACTION);
|
||||
serviceId = GmsService.RECAPTCHA.SERVICE_ID;
|
||||
}
|
||||
|
||||
public void init(IInitCallback callback, String siteKey) throws RemoteException {
|
||||
getServiceInterface().init(callback, siteKey);
|
||||
}
|
||||
|
||||
public void init(IInitCallback callback, InitParams params) throws RemoteException {
|
||||
getServiceInterface().init2(callback, params);
|
||||
}
|
||||
|
||||
public void execute(IExecuteCallback callback, RecaptchaHandle handle, RecaptchaAction action) throws RemoteException {
|
||||
getServiceInterface().execute(callback, handle, action);
|
||||
}
|
||||
|
||||
public void execute(IExecuteCallback callback, ExecuteParams params) throws RemoteException {
|
||||
getServiceInterface().execute2(callback, params);
|
||||
}
|
||||
|
||||
public void close(ICloseCallback callback, RecaptchaHandle handle) throws RemoteException {
|
||||
getServiceInterface().close(callback, handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IRecaptchaService interfaceFromBinder(IBinder binder) {
|
||||
return IRecaptchaService.Stub.asInterface(binder);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue