From 93ccdbdeb70c585edd74a3f9ae84057065fb85d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 15 Oct 2022 21:24:30 +0200 Subject: [PATCH] [API/Login] Make user action handling more universal. --- .../szczodrzynski/edziennik/MainActivity.kt | 16 +- .../edziennik/data/api/ApiService.kt | 18 +- .../edziennik/data/api/Constants.kt | 3 + .../edziennik/data/api/Errors.kt | 7 +- .../data/api/edziennik/librus/Librus.kt | 2 + .../librus/login/LibrusLoginPortal.kt | 22 ++- .../edziennik/mobidziennik/Mobidziennik.kt | 2 + .../data/api/edziennik/podlasie/Podlasie.kt | 5 + .../data/api/edziennik/template/Template.kt | 5 + .../edziennik/data/api/edziennik/usos/Usos.kt | 5 + .../api/edziennik/usos/login/UsosLoginApi.kt | 18 +- .../data/api/edziennik/vulcan/Vulcan.kt | 2 + .../api/events/UserActionRequiredEvent.kt | 16 +- .../data/api/interfaces/EdziennikCallback.kt | 2 + .../edziennik/data/api/models/Data.kt | 15 +- .../edziennik/ext/BundleExtensions.kt | 10 ++ ...tchaDialog.kt => RecaptchaPromptDialog.kt} | 14 +- .../edziennik/ui/home/cards/HomeDebugCard.kt | 7 +- .../edziennik/ui/login/LoginInfo.kt | 1 - .../ui/login/LoginProgressFragment.kt | 25 ++- .../utils/managers/UserActionManager.kt | 164 ++++++++---------- app/src/main/res/layout/card_home_debug.xml | 7 - app/src/main/res/values/errors.xml | 14 -- 23 files changed, 206 insertions(+), 174 deletions(-) rename app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/{LibrusCaptchaDialog.kt => RecaptchaPromptDialog.kt} (88%) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 30c3a3e0..000f9be6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -34,6 +34,7 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.droidsonroids.gif.GifDrawable +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUIRES_USER_ACTION import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.* @@ -69,6 +70,7 @@ import pl.szczodrzynski.edziennik.ui.grades.editor.GradesEditorFragment import pl.szczodrzynski.edziennik.ui.home.HomeFragment import pl.szczodrzynski.edziennik.ui.homework.HomeworkFragment import pl.szczodrzynski.edziennik.ui.login.LoginActivity +import pl.szczodrzynski.edziennik.ui.login.LoginProgressFragment import pl.szczodrzynski.edziennik.ui.messages.compose.MessagesComposeFragment import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment import pl.szczodrzynski.edziennik.ui.messages.single.MessageFragment @@ -83,6 +85,7 @@ import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.dpToPx import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type +import pl.szczodrzynski.edziennik.utils.managers.UserActionManager import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.NavTarget import pl.szczodrzynski.navlib.* @@ -853,7 +856,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope { @Subscribe(threadMode = ThreadMode.MAIN) fun onUserActionRequiredEvent(event: UserActionRequiredEvent) { - app.userActionManager.execute(this, event.profileId, event.type, event.params) + app.userActionManager.execute(this, event, UserActionManager.UserActionCallback()) } private fun fragmentToSyncName(currentFragment: Int): Int { @@ -911,12 +914,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope { false } "userActionRequired" -> { - app.userActionManager.execute( - this, - extras.getInt("profileId"), - extras.getInt("type"), - extras.getBundle("params"), + val event = UserActionRequiredEvent( + profileId = extras.getInt("profileId"), + type = extras.getEnum("type") ?: return, + params = extras.getBundle("params") ?: return, + errorText = 0, ) + app.userActionManager.execute(this, event, UserActionManager.UserActionCallback()) true } "createManualEvent" -> { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt index f68e8840..0d9fe92c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt @@ -84,19 +84,21 @@ class ApiService : Service() { runTask() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + app.userActionManager.sendToUser(event) + taskRunning?.cancel() + clearTask() + runTask() + } + override fun onError(apiError: ApiError) { lastEventTime = System.currentTimeMillis() d(TAG, "Task $taskRunningId threw an error - $apiError") apiError.profileId = taskProfileId - if (app.userActionManager.requiresUserAction(apiError)) { - app.userActionManager.sendToUser(apiError) - } - else { - EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError)) - errorList.add(apiError) - apiError.throwable?.printStackTrace() - } + EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError)) + errorList.add(apiError) + apiError.throwable?.printStackTrace() if (apiError.isCritical) { taskRunning?.cancel() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt index c0318251..c29b8698 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -59,6 +59,9 @@ const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action=" const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile" const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik" +const val LIBRUS_PORTAL_RECAPTCHA_KEY = "6Lf48moUAAAAAB9ClhdvHr46gRWR" +const val LIBRUS_PORTAL_RECAPTCHA_REFERER = "https://portal.librus.pl/rodzina/login" + val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index 10405e5f..b4e892e6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -58,11 +58,7 @@ const val ERROR_INVALID_LOGIN_MODE = 110 const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 const val ERROR_NOT_IMPLEMENTED = 112 const val ERROR_FILE_DOWNLOAD = 113 - -const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115 - -const val ERROR_CAPTCHA_NEEDED = 3000 -const val ERROR_CAPTCHA_LIBRUS_PORTAL = 3001 +const val ERROR_REQUIRES_USER_ACTION = 114 const val ERROR_API_PDO_ERROR = 5000 const val ERROR_API_INVALID_CLIENT = 5001 @@ -204,7 +200,6 @@ const val ERROR_PODLASIE_API_NO_TOKEN = 630 const val ERROR_PODLASIE_API_OTHER = 631 const val ERROR_PODLASIE_API_DATA_MISSING = 632 -const val ERROR_USOS_OAUTH_LOGIN_REQUEST = 701 const val ERROR_USOS_OAUTH_GOT_DIFFERENT_TOKEN = 702 const val ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE = 703 const val ERROR_USOS_NO_STUDENT_PROGRAMMES = 704 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt index abf3ac5f..0830752f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.Librus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin.LibrusFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -162,6 +163,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { override fun onCompleted() { callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) } override fun onProgress(step: Float) { callback.onProgress(step) } override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt index ed01d01f..ab21c692 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt @@ -10,6 +10,7 @@ import im.wangchao.mhttp.callback.TextCallbackHandler import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.utils.Utils.d @@ -148,12 +149,23 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { val error = if (response.code() == 200) null else json.getJsonArray("errors")?.getString(0) ?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString + + if (error?.contains("robotem") == true || json.getBoolean("captchaRequired") == true) { + data.requireUserAction( + type = UserActionRequiredEvent.Type.RECAPTCHA, + params = Bundle( + "siteKey" to LIBRUS_PORTAL_RECAPTCHA_KEY, + "referer" to LIBRUS_PORTAL_RECAPTCHA_REFERER, + ), + errorText = R.string.notification_user_action_required_captcha_librus, + ) + return + } + error?.let { code -> when { code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN - // this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set - code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR }.let { errorCode -> @@ -163,12 +175,6 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { return } } - if (json.getBoolean("captchaRequired") == true) { - data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL) - .withResponse(response) - .withApiResponse(json)) - return - } authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL)) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt index e19fabca..0144ad5d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.* import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.firstlogin.MobidziennikFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -142,6 +143,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { override fun onCompleted() { callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) } override fun onProgress(step: Float) { callback.onProgress(step) } override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt index d7ec441f..0eaaebae 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/Podlasie.kt @@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieData import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin.PodlasieFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLogin import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -142,6 +143,10 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore, callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + callback.onRequiresUserAction(event) + } + override fun onProgress(step: Float) { callback.onProgress(step) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt index 843d36e9..8abf079f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/template/Template.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.CODE_INTERNAL_LIBRUS_ACCOUNT_410 import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.TemplateData import pl.szczodrzynski.edziennik.data.api.edziennik.template.firstlogin.TemplateFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -108,6 +109,10 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + callback.onRequiresUserAction(event) + } + override fun onProgress(step: Float) { callback.onProgress(step) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt index 4ff9d04c..d676d006 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/Usos.kt @@ -8,6 +8,7 @@ import com.google.gson.JsonObject import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.data.api.edziennik.usos.firstlogin.UsosFirstLogin import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLogin +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -88,6 +89,10 @@ class Usos( callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { + callback.onRequiresUserAction(event) + } + override fun onProgress(step: Float) { callback.onProgress(step) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt index f49c0cea..11172d7d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/usos/login/UsosLoginApi.kt @@ -4,9 +4,11 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.usos.login +import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.ext.Bundle import pl.szczodrzynski.edziennik.ext.fromQueryString @@ -53,13 +55,16 @@ class UsosLoginApi(val data: DataUsos, val onSuccess: () -> Unit) { "interactivity" to "confirm_user", "oauth_token" to (data.oauthTokenKey ?: ""), ) - val params = Bundle( - "authorizeUrl" to "$authUrl?${authParams.toQueryString()}", - "redirectUrl" to USOS_API_OAUTH_REDIRECT_URL, - "responseStoreKey" to "oauthLoginResponse", - "extras" to data.loginStore.data.toBundle(), + data.requireUserAction( + type = UserActionRequiredEvent.Type.OAUTH, + params = Bundle( + "authorizeUrl" to "$authUrl?${authParams.toQueryString()}", + "redirectUrl" to USOS_API_OAUTH_REDIRECT_URL, + "responseStoreKey" to "oauthLoginResponse", + "extras" to data.loginStore.data.toBundle(), + ), + errorText = R.string.notification_user_action_required_oauth_usos, ) - data.error(ApiError(TAG, ERROR_USOS_OAUTH_LOGIN_REQUEST).withParams(params)) } } @@ -93,6 +98,7 @@ class UsosLoginApi(val data: DataUsos, val onSuccess: () -> Unit) { data.oauthTokenKey = accessData["oauth_token"] data.oauthTokenSecret = accessData["oauth_token_secret"] data.oauthTokenIsUser = data.oauthTokenKey != null && data.oauthTokenSecret != null + data.loginStore.removeLoginData("oauthLoginResponse") if (!data.oauthTokenIsUser) data.error(ApiError(TAG, ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt index 64c87bf6..37af4b69 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/Vulcan.kt @@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.data.api.models.ApiError @@ -179,6 +180,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { return object : EdziennikCallback { override fun onCompleted() { callback.onCompleted() } + override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) } override fun onProgress(step: Float) { callback.onProgress(step) } override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } override fun onError(apiError: ApiError) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt index da68f3e8..3995842a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/UserActionRequiredEvent.kt @@ -6,12 +6,14 @@ package pl.szczodrzynski.edziennik.data.api.events import android.os.Bundle -data class UserActionRequiredEvent(val profileId: Int, val type: Int, val params: Bundle?) { - companion object { - const val LOGIN_DATA_MOBIDZIENNIK = 101 - const val LOGIN_DATA_LIBRUS = 102 - const val LOGIN_DATA_VULCAN = 104 - const val CAPTCHA_LIBRUS = 202 - const val OAUTH_USOS = 701 +data class UserActionRequiredEvent( + val profileId: Int?, + val type: Type, + val params: Bundle, + val errorText: Int, +) { + enum class Type { + RECAPTCHA, + OAUTH, } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt index c1c878b4..4943ff1e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/interfaces/EdziennikCallback.kt @@ -4,6 +4,7 @@ package pl.szczodrzynski.edziennik.data.api.interfaces +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent import pl.szczodrzynski.edziennik.data.api.models.Feature import pl.szczodrzynski.edziennik.data.api.models.LoginMethod @@ -14,4 +15,5 @@ import pl.szczodrzynski.edziennik.data.api.models.LoginMethod */ interface EdziennikCallback : EndpointCallback { fun onCompleted() + fun onRequiresUserAction(event: UserActionRequiredEvent) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index ea53112a..c02c666a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -1,5 +1,6 @@ package pl.szczodrzynski.edziennik.data.api.models +import android.os.Bundle import android.util.LongSparseArray import android.util.SparseArray import androidx.core.util.set @@ -12,7 +13,8 @@ import pl.szczodrzynski.edziennik.BuildConfig import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META -import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback +import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.ext.* @@ -37,7 +39,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt /** * A callback passed to all [Feature]s and [LoginMethod]s */ - lateinit var callback: EndpointCallback + lateinit var callback: EdziennikCallback /** * A list of [LoginMethod]s *already fulfilled* during this sync. @@ -374,6 +376,15 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt callback.onError(apiError) } + fun requireUserAction(type: UserActionRequiredEvent.Type, params: Bundle, errorText: Int) { + callback.onRequiresUserAction(UserActionRequiredEvent( + profileId = profile?.id, + type = type, + params = params, + errorText = errorText, + )) + } + fun progress(step: Float) { callback.onProgress(step) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt index cc6488bd..037d78f5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ext/BundleExtensions.kt @@ -22,6 +22,15 @@ fun Bundle?.getFloat(key: String, defaultValue: Float): Float { fun Bundle?.getString(key: String, defaultValue: String): String { return this?.getString(key, defaultValue) ?: defaultValue } +inline fun > Bundle?.getEnum(key: String): E? { + return this?.getString(key)?.let { + try { + enumValueOf(it) + } catch (e: Exception) { + null + } + } +} fun Bundle?.getIntOrNull(key: String): Int? { return this?.get(key) as? Int @@ -48,6 +57,7 @@ fun Bundle(vararg properties: Pair): Bundle { is Bundle -> putBundle(property.first, property.second as Bundle) is Parcelable -> putParcelable(property.first, property.second as Parcelable) is Array<*> -> putParcelableArray(property.first, property.second as Array) + is Enum<*> -> putString(property.first, (property.second as Enum<*>).name) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/LibrusCaptchaDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt similarity index 88% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/LibrusCaptchaDialog.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt index 12ad0e37..a927347d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/LibrusCaptchaDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt @@ -13,14 +13,16 @@ import pl.szczodrzynski.edziennik.databinding.RecaptchaViewBinding import pl.szczodrzynski.edziennik.ext.onClick import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog -class LibrusCaptchaDialog( +class RecaptchaPromptDialog( activity: AppCompatActivity, + private val siteKey: String, + private val referer: String, private val onSuccess: (recaptchaCode: String) -> Unit, - private val onFailure: (() -> Unit)?, + private val onCancel: (() -> Unit)?, onShowListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null, ) : BindingDialog(activity, onShowListener, onDismissListener) { - override val TAG = "LibrusCaptchaDialog" + override val TAG = "RecaptchaPromptDialog" override fun getTitleRes(): Int? = null override fun inflate(layoutInflater: LayoutInflater) = @@ -46,8 +48,8 @@ class LibrusCaptchaDialog( b.progress.visibility = View.VISIBLE RecaptchaDialog( activity, - siteKey = "6Lf48moUAAAAAB9ClhdvHr46gRWR-CN31CXQPG2U", - referer = "https://portal.librus.pl/rodzina/login", + siteKey = siteKey, + referer = referer, onSuccess = { recaptchaCode -> b.checkbox.background = checkboxBackground b.checkbox.foreground = checkboxForeground @@ -67,6 +69,6 @@ class LibrusCaptchaDialog( override fun onDismiss() { if (!success) - onFailure?.invoke() + onCancel?.invoke() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt index 329a677a..6947b74b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/home/cards/HomeDebugCard.kt @@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.CardHomeDebugBinding import pl.szczodrzynski.edziennik.ext.dp import pl.szczodrzynski.edziennik.ext.onClick -import pl.szczodrzynski.edziennik.ui.captcha.LibrusCaptchaDialog +import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog import pl.szczodrzynski.edziennik.ui.home.HomeCard import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.home.HomeFragment @@ -85,11 +85,6 @@ class HomeDebugCard( app.startActivity(Chucker.getLaunchIntent(activity, 1)); } - b.librusCaptchaButton.onClick { - //app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java)) - LibrusCaptchaDialog(activity, onSuccess = {}, onFailure = {}).show() - } - b.getLogs.onClick { val logs = HyperLog.getDeviceLogsInFile(activity, true) val intent = Intent(Intent.ACTION_SEND) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt index 8b605734..774530f1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginInfo.kt @@ -65,7 +65,6 @@ object LoginInfo { errorCodes = mapOf( ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED to R.string.login_error_account_not_activated, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password, - ERROR_CAPTCHA_LIBRUS_PORTAL to R.string.error_3001_reason ) ), /*Mode( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt index ee29575e..87a01008 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/LoginProgressFragment.kt @@ -19,7 +19,7 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_NEEDED +import pl.szczodrzynski.edziennik.data.api.ERROR_REQUIRES_USER_ACTION import pl.szczodrzynski.edziennik.data.api.LOGIN_NO_ARGUMENTS import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent @@ -29,6 +29,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.data.db.entity.LoginStore import pl.szczodrzynski.edziennik.databinding.LoginProgressFragmentBinding import pl.szczodrzynski.edziennik.ext.joinNotNullStrings +import pl.szczodrzynski.edziennik.utils.managers.UserActionManager import kotlin.coroutines.CoroutineContext import kotlin.math.max @@ -137,13 +138,21 @@ class LoginProgressFragment : Fragment(), CoroutineScope { return } - app.userActionManager.execute(activity, event.profileId, event.type, event.params, onSuccess = { params -> - args.putAll(params) - doFirstLogin(args) - }, onFailure = { - activity.error(ApiError(TAG, ERROR_CAPTCHA_NEEDED)) - nav.navigateUp() - }) + val callback = UserActionManager.UserActionCallback( + onSuccess = { data -> + args.putAll(data) + doFirstLogin(args) + }, + onFailure = { + activity.error(ApiError(TAG, ERROR_REQUIRES_USER_ACTION)) + nav.navigateUp() + }, + onCancel = { + nav.navigateUp() + }, + ) + + app.userActionManager.execute(activity, event, callback) } override fun onStart() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt index 3117c650..3e8761c8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt @@ -16,13 +16,10 @@ import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_LIBRUS_PORTAL -import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_OAUTH_LOGIN_REQUEST import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent -import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.ext.* -import pl.szczodrzynski.edziennik.ui.captcha.LibrusCaptchaDialog +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.utils.Utils.d @@ -32,43 +29,31 @@ class UserActionManager(val app: App) { private const val TAG = "UserActionManager" } - fun requiresUserAction(apiError: ApiError) = when (apiError.errorCode) { - ERROR_CAPTCHA_LIBRUS_PORTAL -> true - ERROR_USOS_OAUTH_LOGIN_REQUEST -> true - else -> false - } - - fun sendToUser(apiError: ApiError) { - val type = when (apiError.errorCode) { - ERROR_CAPTCHA_LIBRUS_PORTAL -> UserActionRequiredEvent.CAPTCHA_LIBRUS - ERROR_USOS_OAUTH_LOGIN_REQUEST -> UserActionRequiredEvent.OAUTH_USOS - else -> 0 - } - + fun sendToUser(event: UserActionRequiredEvent) { if (EventBus.getDefault().hasSubscriberForEvent(UserActionRequiredEvent::class.java)) { - EventBus.getDefault().post(UserActionRequiredEvent(apiError.profileId ?: -1, type, apiError.params)) + EventBus.getDefault().post(event) return } val manager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val text = app.getString(when (type) { - UserActionRequiredEvent.CAPTCHA_LIBRUS -> R.string.notification_user_action_required_captcha_librus - UserActionRequiredEvent.OAUTH_USOS -> R.string.notification_user_action_required_oauth_usos - else -> R.string.notification_user_action_required_text - }, apiError.profileId) - + val text = app.getString(event.errorText, event.profileId) val intent = Intent( - app, - MainActivity::class.java, - "action" to "userActionRequired", - "profileId" to (apiError.profileId ?: -1), - "type" to type, - "params" to apiError.params, + app, + MainActivity::class.java, + "action" to "userActionRequired", + "profileId" to event.profileId, + "type" to event.type, + "params" to event.params, + ) + val pendingIntent = PendingIntent.getActivity( + app, + System.currentTimeMillis().toInt(), + intent, + PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag(), ) - val pendingIntent = PendingIntent.getActivity(app, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag()) - val notification = NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key) + val notification = + NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key) .setContentTitle(app.getString(R.string.notification_user_action_required_title)) .setContentText(text) .setSmallIcon(R.drawable.ic_error_outline) @@ -84,70 +69,56 @@ class UserActionManager(val app: App) { manager.notify(System.currentTimeMillis().toInt(), notification) } + class UserActionCallback( + val onSuccess: ((data: Bundle) -> Unit)? = null, + val onFailure: (() -> Unit)? = null, + val onCancel: (() -> Unit)? = null, + ) + fun execute( - activity: AppCompatActivity, - profileId: Int?, - type: Int, - params: Bundle? = null, - onSuccess: ((params: Bundle) -> Unit)? = null, - onFailure: (() -> Unit)? = null + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, ) { - d(TAG, "Running user action ($type) with params: ${params?.toJsonObject()}") - val isSuccessful = when (type) { - UserActionRequiredEvent.CAPTCHA_LIBRUS -> executeLibrus(activity, profileId, params, onSuccess, onFailure) - UserActionRequiredEvent.OAUTH_USOS -> executeOauth(activity, profileId, params, onSuccess, onFailure) - else -> false - } - if (!isSuccessful) { - onFailure?.invoke() + d(TAG, "Running user action (${event.type}) with params: ${event.params}") + val isSuccessful = when (event.type) { + UserActionRequiredEvent.Type.RECAPTCHA -> executeRecaptcha(activity, event, callback) + UserActionRequiredEvent.Type.OAUTH -> executeOauth(activity, event, callback) } + if (!isSuccessful) + callback.onFailure?.invoke() } - private fun executeLibrus( + private fun executeRecaptcha( activity: AppCompatActivity, - profileId: Int?, - params: Bundle?, - onSuccess: ((params: Bundle) -> Unit)?, - onFailure: (() -> Unit)?, + event: UserActionRequiredEvent, + callback: UserActionCallback, ): Boolean { - if (profileId == null) - return false - val extras = params?.getBundle("extras") - // show captcha dialog - // use passed onSuccess listener, else sync profile - LibrusCaptchaDialog( + val siteKey = event.params.getString("siteKey") ?: return false + val referer = event.params.getString("referer") ?: return false + RecaptchaPromptDialog( activity = activity, + siteKey = siteKey, + referer = referer, onSuccess = { code -> - val args = Bundle( + finishAction(activity, event, callback, Bundle( "recaptchaCode" to code, "recaptchaTime" to System.currentTimeMillis(), - ) - if (extras != null) - args.putAll(extras) - - if (onSuccess != null) - onSuccess(args) - else - EdziennikTask.syncProfile(profileId, arguments = args.toJsonObject()).enqueue(activity) + )) }, - onFailure = onFailure, + onCancel = callback.onCancel, ).show() return true } private fun executeOauth( activity: AppCompatActivity, - profileId: Int?, - params: Bundle?, - onSuccess: ((params: Bundle) -> Unit)?, - onFailure: (() -> Unit)?, + event: UserActionRequiredEvent, + callback: UserActionCallback, ): Boolean { - if (profileId == null || params == null) - return false - val extras = params.getBundle("extras") - val storeKey = params.getString("responseStoreKey") ?: return false - params.getString("authorizeUrl") ?: return false - params.getString("redirectUrl") ?: return false + val storeKey = event.params.getString("responseStoreKey") ?: return false + event.params.getString("authorizeUrl") ?: return false + event.params.getString("redirectUrl") ?: return false var listener: Any? = null listener = object { @@ -155,26 +126,41 @@ class UserActionManager(val app: App) { fun onOAuthLoginResult(result: OAuthLoginResult) { EventBus.getDefault().unregister(listener) when { - result.isError -> onFailure?.invoke() + result.isError -> callback.onFailure?.invoke() result.responseUrl != null -> { - val args = Bundle( + finishAction(activity, event, callback, Bundle( storeKey to result.responseUrl, - ) - if (extras != null) - args.putAll(extras) - - if (onSuccess != null) - onSuccess(args) - else - EdziennikTask.syncProfile(profileId, arguments = args.toJsonObject()).enqueue(activity) + )) } + else -> callback.onCancel?.invoke() } } } EventBus.getDefault().register(listener) - val intent = Intent(activity, OAuthLoginActivity::class.java).putExtras(params) + val intent = Intent(activity, OAuthLoginActivity::class.java).putExtras(event.params) activity.startActivity(intent) return true } + + private fun finishAction( + activity: AppCompatActivity, + event: UserActionRequiredEvent, + callback: UserActionCallback, + data: Bundle, + ) { + val extras = event.params.getBundle("extras") + if (extras != null) + data.putAll(extras) + + if (callback.onSuccess != null) + callback.onSuccess.invoke(data) + else if (event.profileId != null) + EdziennikTask.syncProfile( + profileId = event.profileId, + arguments = data.toJsonObject(), + ).enqueue(activity) + else + callback.onFailure?.invoke() + } } diff --git a/app/src/main/res/layout/card_home_debug.xml b/app/src/main/res/layout/card_home_debug.xml index 18d21c02..4bcca808 100644 --- a/app/src/main/res/layout/card_home_debug.xml +++ b/app/src/main/res/layout/card_home_debug.xml @@ -25,13 +25,6 @@ android:layout_height="wrap_content" android:text="Save Debug Logs" /> - - ERROR_NOT_IMPLEMENTED ERROR_FILE_DOWNLOAD - ERROR_NO_STUDENTS_IN_ACCOUNT - - ERROR_CAPTCHA_NEEDED - ERROR_CAPTCHA_LIBRUS_PORTAL - ERROR_API_PDO_ERROR ERROR_API_INVALID_CLIENT ERROR_API_INVALID_ARGUMENT @@ -174,8 +169,6 @@ ERROR_PODLASIE_API_OTHER ERROR_PODLASIE_API_DATA_MISSING - ERROR_USOS_OAUTH_LOGIN_REQUEST - ERROR_TEMPLATE_WEB_OTHER EXCEPTION_API_TASK @@ -223,11 +216,6 @@ Nie zaimplementowano Wystąpił błąd podczas pobierania pliku. Dziennik może być przeciążony lub mieć przerwę techniczną. - Brak uczniów przypisanych do konta - - Wymagane rozwiązanie zadania Captcha - LIBRUS®️: wymagane rozwiązanie zadania Captcha - ERROR_API_PDO_ERROR Nieprawidłowy ID klienta API API: nieprawidłowy argument @@ -366,8 +354,6 @@ ERROR_PODLASIE_API_OTHER Brak danych. Zgłoś błąd programiście. - Wymagane logowanie w przeglądarce - ERROR_TEMPLATE_WEB_OTHER Błąd synchronizacji. Upewnij się, że masz połączenie z internetem, a następnie zgłoś błąd.