mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2024-11-24 10:54:36 -06:00
[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"
|
<activity android:name=".ui.login.oauth.OAuthLoginActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:exported="false"
|
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.base.BuildInvalidActivity" android:exported="false" />
|
||||||
<activity android:name=".ui.settings.contributors.ContributorsActivity" 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 autoRetry: Boolean = true,
|
||||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||||
private val onFailure: (() -> Unit)? = null,
|
private val onFailure: (() -> Unit)? = null,
|
||||||
|
private val onServerError: (() -> Unit)? = null,
|
||||||
onShowListener: ((tag: String) -> Unit)? = null,
|
onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||||
) : BindingDialog<RecaptchaDialogBinding>(activity, onShowListener, onDismissListener) {
|
) : BindingDialog<RecaptchaDialogBinding>(activity, onShowListener, onDismissListener) {
|
||||||
@ -44,7 +45,11 @@ class RecaptchaDialog(
|
|||||||
|
|
||||||
override suspend fun onBeforeShow(): Boolean {
|
override suspend fun onBeforeShow(): Boolean {
|
||||||
val (title, text, bitmap) = withContext(Dispatchers.Default) {
|
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)
|
return@withContext loadCaptchaData(html)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
onFailure?.invoke()
|
onFailure?.invoke()
|
||||||
|
@ -19,6 +19,7 @@ class RecaptchaPromptDialog(
|
|||||||
private val referer: String,
|
private val referer: String,
|
||||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||||
private val onCancel: (() -> Unit)?,
|
private val onCancel: (() -> Unit)?,
|
||||||
|
private val onServerError: (() -> Unit)? = null,
|
||||||
onShowListener: ((tag: String) -> Unit)? = null,
|
onShowListener: ((tag: String) -> Unit)? = null,
|
||||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||||
) : BindingDialog<RecaptchaViewBinding>(activity, onShowListener, onDismissListener) {
|
) : BindingDialog<RecaptchaViewBinding>(activity, onShowListener, onDismissListener) {
|
||||||
@ -62,7 +63,8 @@ class RecaptchaPromptDialog(
|
|||||||
b.checkbox.background = checkboxBackground
|
b.checkbox.background = checkboxBackground
|
||||||
b.checkbox.foreground = checkboxForeground
|
b.checkbox.foreground = checkboxForeground
|
||||||
b.progress.visibility = View.GONE
|
b.progress.visibility = View.GONE
|
||||||
}
|
},
|
||||||
|
onServerError = onServerError,
|
||||||
).show()
|
).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.captcha.RecaptchaPromptDialog
|
||||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginActivity
|
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginActivity
|
||||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginResult
|
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
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
|
|
||||||
class UserActionManager(val app: App) {
|
class UserActionManager(val app: App) {
|
||||||
@ -107,10 +109,45 @@ class UserActionManager(val app: App) {
|
|||||||
))
|
))
|
||||||
},
|
},
|
||||||
onCancel = callback.onCancel,
|
onCancel = callback.onCancel,
|
||||||
|
onServerError = {
|
||||||
|
executeRecaptchaActivity(activity, event, callback)
|
||||||
|
},
|
||||||
).show()
|
).show()
|
||||||
return true
|
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(
|
private fun executeOauth(
|
||||||
activity: AppCompatActivity,
|
activity: AppCompatActivity,
|
||||||
event: UserActionRequiredEvent,
|
event: UserActionRequiredEvent,
|
||||||
|
@ -1543,6 +1543,7 @@
|
|||||||
<string name="login_mode_usos_oauth_guide">TODO</string>
|
<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="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="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="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_received">{cmd-share-variant} udostępnione w klasie</string>
|
||||||
<string name="legend_event_shared_sent">{cmd-share-variant} udostępnione przez Ciebie</string>
|
<string name="legend_event_shared_sent">{cmd-share-variant} udostępnione przez Ciebie</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user