forked from github/wulkanowy-mirror
Add webview to obtain cloudflare captcha cookies for okhttp (#2392)
This commit is contained in:
parent
d8c4926a97
commit
a98e8398fd
@ -193,7 +193,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'io.github.wulkanowy:sdk:2.3.5'
|
implementation 'io.github.wulkanowy:sdk:2.3.6-SNAPSHOT'
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||||
|
|
||||||
@ -238,6 +238,7 @@ dependencies {
|
|||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
|
||||||
|
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||||
|
|
||||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||||
implementation 'com.github.Faierbel:slf4j-timber:2.0'
|
implementation 'com.github.Faierbel:slf4j-timber:2.0'
|
||||||
|
@ -21,6 +21,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
|
|||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import io.github.wulkanowy.utils.RemoteConfigHelper
|
import io.github.wulkanowy.utils.RemoteConfigHelper
|
||||||
|
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -43,6 +44,7 @@ internal class DataModule {
|
|||||||
buildTag = android.os.Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
userAgentTemplate = remoteConfig.userAgentTemplate
|
userAgentTemplate = remoteConfig.userAgentTemplate
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
|
setAdditionalCookieManager(WebkitCookieManagerProxy())
|
||||||
|
|
||||||
// for debug only
|
// for debug only
|
||||||
addInterceptor(chuckerInterceptor, network = true)
|
addInterceptor(chuckerInterceptor, network = true)
|
||||||
|
@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
import io.github.wulkanowy.ui.modules.auth.AuthDialog
|
||||||
|
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
|
||||||
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
import io.github.wulkanowy.ui.modules.login.LoginActivity
|
||||||
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
import io.github.wulkanowy.utils.FragmentLifecycleLogger
|
||||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
@ -77,6 +78,10 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog")
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.main_session_expired)
|
.setTitle(R.string.main_session_expired)
|
||||||
|
@ -32,6 +32,10 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,10 @@ abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragme
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ open class BasePresenter<T : BaseView>(
|
|||||||
errorHandler.apply {
|
errorHandler.apply {
|
||||||
showErrorMessage = view::showError
|
showErrorMessage = view::showError
|
||||||
onExpiredCredentials = view::showExpiredCredentialsDialog
|
onExpiredCredentials = view::showExpiredCredentialsDialog
|
||||||
|
onCaptchaVerificationRequired = view::onCaptchaVerificationRequired
|
||||||
onDecryptionFailed = view::showDecryptionFailedDialog
|
onDecryptionFailed = view::showDecryptionFailedDialog
|
||||||
onNoCurrentStudent = view::openClearLoginView
|
onNoCurrentStudent = view::openClearLoginView
|
||||||
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
||||||
|
@ -8,6 +8,8 @@ interface BaseView {
|
|||||||
|
|
||||||
fun showExpiredCredentialsDialog()
|
fun showExpiredCredentialsDialog()
|
||||||
|
|
||||||
|
fun onCaptchaVerificationRequired(url: String?)
|
||||||
|
|
||||||
fun showDecryptionFailedDialog()
|
fun showDecryptionFailedDialog()
|
||||||
|
|
||||||
fun showAuthDialog()
|
fun showAuthDialog()
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
|
import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException
|
||||||
|
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||||
import io.github.wulkanowy.utils.getErrorString
|
import io.github.wulkanowy.utils.getErrorString
|
||||||
@ -25,6 +26,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
|||||||
|
|
||||||
var onAuthorizationRequired: () -> Unit = {}
|
var onAuthorizationRequired: () -> Unit = {}
|
||||||
|
|
||||||
|
var onCaptchaVerificationRequired: (url: String?) -> Unit = {}
|
||||||
|
|
||||||
fun dispatch(error: Throwable) {
|
fun dispatch(error: Throwable) {
|
||||||
Timber.e(error, "An exception occurred while the Wulkanowy was running")
|
Timber.e(error, "An exception occurred while the Wulkanowy was running")
|
||||||
proceed(error)
|
proceed(error)
|
||||||
@ -38,6 +41,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
|||||||
is BadCredentialsException -> onExpiredCredentials()
|
is BadCredentialsException -> onExpiredCredentials()
|
||||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||||
is AuthorizationRequiredException -> onAuthorizationRequired()
|
is AuthorizationRequiredException -> onAuthorizationRequired()
|
||||||
|
is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules.captcha
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.WebResourceError
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.databinding.DialogCaptchaBinding
|
||||||
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.ui.base.BaseDialogFragment
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var sdk: Sdk
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CAPTCHA_URL = "captcha_url"
|
||||||
|
fun newInstance(url: String?): CaptchaDialog {
|
||||||
|
return CaptchaDialog().apply {
|
||||||
|
arguments = bundleOf(CAPTCHA_URL to url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
with(binding.captchaWebview) {
|
||||||
|
with(settings) {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
userAgentString = sdk.userAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
view?.evaluateJavascript("document.getElementById('challenge-running') == undefined") {
|
||||||
|
if (it == "true") {
|
||||||
|
dismiss()
|
||||||
|
} else Timber.e("JS result: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceivedError(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?,
|
||||||
|
error: WebResourceError?
|
||||||
|
) {
|
||||||
|
super.onReceivedError(view, request, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.ui.base.BaseActivity
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.main.MainView
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ -26,6 +27,8 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin
|
|||||||
|
|
||||||
override fun showExpiredCredentialsDialog() {}
|
override fun showExpiredCredentialsDialog() {}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) = Unit
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {}
|
override fun showDecryptionFailedDialog() {}
|
||||||
|
|
||||||
override fun openClearLoginView() {}
|
override fun openClearLoginView() {}
|
||||||
|
@ -51,6 +51,10 @@ class AdvancedFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,10 @@ class AppearanceFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -137,6 +137,10 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,10 @@ class SyncFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package io.github.wulkanowy.utils
|
||||||
|
|
||||||
|
import java.net.CookiePolicy
|
||||||
|
import java.net.URI
|
||||||
|
import android.webkit.CookieManager as WebkitCookieManager
|
||||||
|
import java.net.CookieManager as JavaCookieManager
|
||||||
|
|
||||||
|
class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
|
||||||
|
|
||||||
|
private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance()
|
||||||
|
|
||||||
|
override fun put(uri: URI?, responseHeaders: Map<String?, List<String?>>?) {
|
||||||
|
if (uri == null || responseHeaders == null) return
|
||||||
|
val url = uri.toString()
|
||||||
|
for (headerKey in responseHeaders.keys) {
|
||||||
|
if (headerKey == null || !(
|
||||||
|
headerKey.equals("Set-Cookie2", ignoreCase = true) ||
|
||||||
|
headerKey.equals("Set-Cookie", ignoreCase = true)
|
||||||
|
)
|
||||||
|
) continue
|
||||||
|
|
||||||
|
// process each of the headers
|
||||||
|
for (headerValue in responseHeaders[headerKey].orEmpty()) {
|
||||||
|
webkitCookieManager.setCookie(url, headerValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun get(
|
||||||
|
uri: URI?,
|
||||||
|
requestHeaders: Map<String?, List<String?>?>?
|
||||||
|
): Map<String, List<String>> {
|
||||||
|
require(!(uri == null || requestHeaders == null)) { "Argument is null" }
|
||||||
|
val res = mutableMapOf<String, List<String>>()
|
||||||
|
val cookie = webkitCookieManager.getCookie(uri.toString())
|
||||||
|
if (cookie != null) res["Cookie"] = listOf(cookie)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
12
app/src/main/res/layout/dialog_captcha.xml
Normal file
12
app/src/main/res/layout/dialog_captcha.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.modules.captcha.CaptchaDialog">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/captcha_webview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -105,6 +105,10 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
|
|||||||
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCaptchaVerificationRequired(url: String?) {
|
||||||
|
(activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url)
|
||||||
|
}
|
||||||
|
|
||||||
override fun showDecryptionFailedDialog() {
|
override fun showDecryptionFailedDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user