forked from github/szkolny
[UI/Login] Fallback reCAPTCHA to WebView activity.
This commit is contained in:
parent
07ab1b984f
commit
db00566ebf
@ -160,7 +160,11 @@
|
||||
<activity android:name=".ui.login.oauth.OAuthLoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:theme="@style/AppTheme.Light" />
|
||||
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
|
||||
<activity android:name=".ui.login.recaptcha.RecaptchaActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
|
||||
<activity android:name=".ui.base.BuildInvalidActivity" android:exported="false" />
|
||||
<activity android:name=".ui.settings.contributors.ContributorsActivity" android:exported="false" />
|
||||
|
||||
|
@ -25,6 +25,7 @@ class RecaptchaDialog(
|
||||
private val autoRetry: Boolean = true,
|
||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
private val onFailure: (() -> Unit)? = null,
|
||||
private val onServerError: (() -> Unit)? = null,
|
||||
onShowListener: ((tag: String) -> Unit)? = null,
|
||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||
) : BindingDialog<RecaptchaDialogBinding>(activity, onShowListener, onDismissListener) {
|
||||
@ -44,7 +45,11 @@ class RecaptchaDialog(
|
||||
|
||||
override suspend fun onBeforeShow(): Boolean {
|
||||
val (title, text, bitmap) = withContext(Dispatchers.Default) {
|
||||
val html = loadCaptchaHtml() ?: return@withContext null
|
||||
val html = loadCaptchaHtml()
|
||||
if (html == null) {
|
||||
onServerError?.invoke()
|
||||
return@withContext null
|
||||
}
|
||||
return@withContext loadCaptchaData(html)
|
||||
} ?: run {
|
||||
onFailure?.invoke()
|
||||
|
@ -19,6 +19,7 @@ class RecaptchaPromptDialog(
|
||||
private val referer: String,
|
||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
private val onCancel: (() -> Unit)?,
|
||||
private val onServerError: (() -> Unit)? = null,
|
||||
onShowListener: ((tag: String) -> Unit)? = null,
|
||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||
) : BindingDialog<RecaptchaViewBinding>(activity, onShowListener, onDismissListener) {
|
||||
@ -62,7 +63,8 @@ class RecaptchaPromptDialog(
|
||||
b.checkbox.background = checkboxBackground
|
||||
b.checkbox.foreground = checkboxForeground
|
||||
b.progress.visibility = View.GONE
|
||||
}
|
||||
},
|
||||
onServerError = onServerError,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2023-3-24.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.login.recaptcha
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT
|
||||
import pl.szczodrzynski.edziennik.utils.Themes
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class RecaptchaActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val TAG = "RecaptchaActivity"
|
||||
|
||||
private const val CODE = """
|
||||
PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds
|
||||
ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj
|
||||
cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxkaXYgaWQ9ImdyIiBzdHlsZT0icG9zaXRpb246YWJzb2x1
|
||||
dGU7dG9wOjUwJTt0cmFuc2Zvcm06dHJhbnNsYXRlKDAsLTUwJSk7Ij48L2Rpdj48YnI+PHNjcmlw
|
||||
dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCJnciIse3NpdGVrZXk6IlNJVEVL
|
||||
RVkiLHRoZW1lOiJUSEVNRSIsY2FsbGJhY2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNr
|
||||
KGUpO30sImV4cGlyZWQtY2FsbGJhY2siOndpbmRvdy5pZi5leHBpcmVkQ2FsbGJhY2ssImVycm9y
|
||||
LWNhbGxiYWNrIjp3aW5kb3cuaWYuZXJyb3JDYWxsYmFja30pO308L3NjcmlwdD48L2JvZHk+PC9o
|
||||
dG1sPg==
|
||||
"""
|
||||
}
|
||||
|
||||
private var isSuccessful = false
|
||||
private lateinit var jsInterface: CaptchaCallbackInterface
|
||||
|
||||
interface CaptchaCallbackInterface {
|
||||
@JavascriptInterface
|
||||
fun callback(recaptchaResponse: String)
|
||||
|
||||
@JavascriptInterface
|
||||
fun expiredCallback()
|
||||
|
||||
@JavascriptInterface
|
||||
fun errorCallback()
|
||||
}
|
||||
|
||||
@SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setTitle(R.string.recaptcha_dialog_title)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
}
|
||||
|
||||
val siteKey = intent.getStringExtra("siteKey") ?: return
|
||||
val referer = intent.getStringExtra("referer") ?: return
|
||||
val userAgent = intent.getStringExtra("userAgent") ?: SYSTEM_USER_AGENT
|
||||
|
||||
val htmlContent = Base64.decode(CODE, Base64.DEFAULT)
|
||||
.toString(Charset.defaultCharset())
|
||||
.replace("THEME", if (Themes.isDark) "dark" else "light")
|
||||
.replace("SITEKEY", siteKey)
|
||||
|
||||
jsInterface = object : CaptchaCallbackInterface {
|
||||
@JavascriptInterface
|
||||
override fun callback(recaptchaResponse: String) {
|
||||
isSuccessful = true
|
||||
EventBus.getDefault().post(
|
||||
RecaptchaResult(
|
||||
isError = false,
|
||||
code = recaptchaResponse,
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
override fun expiredCallback() {
|
||||
isSuccessful = false
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
override fun errorCallback() {
|
||||
isSuccessful = false
|
||||
EventBus.getDefault().post(
|
||||
RecaptchaResult(
|
||||
isError = true,
|
||||
code = null,
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
val webView = WebView(this).apply {
|
||||
setBackgroundColor(Color.TRANSPARENT)
|
||||
settings.javaScriptEnabled = true
|
||||
settings.userAgentString = userAgent
|
||||
addJavascriptInterface(jsInterface, "if")
|
||||
loadDataWithBaseURL(
|
||||
referer,
|
||||
htmlContent,
|
||||
"text/html",
|
||||
"UTF-8",
|
||||
null,
|
||||
)
|
||||
// setLayerType(WebView.LAYER_TYPE_SOFTWARE, null)
|
||||
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
setContentView(webView)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!isSuccessful)
|
||||
EventBus.getDefault().post(
|
||||
RecaptchaResult(
|
||||
isError = false,
|
||||
code = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2023-3-24.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.login.recaptcha
|
||||
|
||||
data class RecaptchaResult(
|
||||
val isError: Boolean,
|
||||
val code: String?,
|
||||
)
|
@ -22,6 +22,8 @@ import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog
|
||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginActivity
|
||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginResult
|
||||
import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaActivity
|
||||
import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaResult
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
|
||||
class UserActionManager(val app: App) {
|
||||
@ -107,10 +109,45 @@ class UserActionManager(val app: App) {
|
||||
))
|
||||
},
|
||||
onCancel = callback.onCancel,
|
||||
onServerError = {
|
||||
executeRecaptchaActivity(activity, event, callback)
|
||||
},
|
||||
).show()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun executeRecaptchaActivity(
|
||||
activity: AppCompatActivity,
|
||||
event: UserActionRequiredEvent,
|
||||
callback: UserActionCallback,
|
||||
): Boolean {
|
||||
event.params.getString("siteKey") ?: return false
|
||||
event.params.getString("referer") ?: return false
|
||||
|
||||
var listener: Any? = null
|
||||
listener = object {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onRecaptchaResult(result: RecaptchaResult) {
|
||||
EventBus.getDefault().unregister(listener)
|
||||
when {
|
||||
result.isError -> callback.onFailure?.invoke()
|
||||
result.code != null -> {
|
||||
finishAction(activity, event, callback, Bundle(
|
||||
"recaptchaCode" to result.code,
|
||||
"recaptchaTime" to System.currentTimeMillis(),
|
||||
))
|
||||
}
|
||||
else -> callback.onCancel?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().register(listener)
|
||||
|
||||
val intent = Intent(activity, RecaptchaActivity::class.java).putExtras(event.params)
|
||||
activity.startActivity(intent)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun executeOauth(
|
||||
activity: AppCompatActivity,
|
||||
event: UserActionRequiredEvent,
|
||||
|
@ -1543,6 +1543,7 @@
|
||||
<string name="login_mode_usos_oauth_guide">TODO</string>
|
||||
<string name="notification_user_action_required_oauth_usos">USOS - wymagane logowanie z użyciem przeglądarki</string>
|
||||
<string name="oauth_dialog_title">Zaloguj się</string>
|
||||
<string name="recaptcha_dialog_title">reCAPTCHA</string>
|
||||
<string name="app_cannot_load_data">Nie można załadować danych aplikacji</string>
|
||||
<string name="legend_event_shared_received">{cmd-share-variant} udostępnione w klasie</string>
|
||||
<string name="legend_event_shared_sent">{cmd-share-variant} udostępnione przez Ciebie</string>
|
||||
|
Loading…
Reference in New Issue
Block a user