From 6fff07e52a0d3abecfcf2ef96ed8bf09647609fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Sun, 12 May 2024 17:32:29 +0200 Subject: [PATCH] Add workarounds to detect not mapped urls as NotLoggedIn errors in multi threaded envs --- sdk-scrapper/api/sdk-scrapper.api | 5 ++ .../io/github/wulkanowy/sdk/scrapper/Utils.kt | 17 ++++++- .../scrapper/exception/VulcanServerError.kt | 9 ++++ .../interceptor/AutoLoginInterceptor.kt | 51 ++++++++++++++----- .../scrapper/interceptor/ErrorInterceptor.kt | 3 +- .../scrapper/repository/RegisterRepository.kt | 10 ++-- .../sdk/scrapper/service/ServiceManager.kt | 2 +- 7 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/exception/VulcanServerError.kt diff --git a/sdk-scrapper/api/sdk-scrapper.api b/sdk-scrapper/api/sdk-scrapper.api index d7d479fe..ecc9c203 100644 --- a/sdk-scrapper/api/sdk-scrapper.api +++ b/sdk-scrapper/api/sdk-scrapper.api @@ -439,6 +439,11 @@ public final class io/github/wulkanowy/sdk/scrapper/exception/VulcanClientError public class io/github/wulkanowy/sdk/scrapper/exception/VulcanException : io/github/wulkanowy/sdk/scrapper/exception/ScrapperException { } +public final class io/github/wulkanowy/sdk/scrapper/exception/VulcanServerError : io/github/wulkanowy/sdk/scrapper/exception/VulcanException { + public final fun getDoc ()Lorg/jsoup/nodes/Document; + public final fun getHttpCode ()I +} + public final class io/github/wulkanowy/sdk/scrapper/grades/Grade { public static final field Companion Lio/github/wulkanowy/sdk/scrapper/grades/Grade$Companion; public field date Ljava/time/LocalDate; diff --git a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/Utils.kt b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/Utils.kt index 54887d0f..f1283677 100644 --- a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/Utils.kt +++ b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/Utils.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.sdk.scrapper.messages.Mailbox import io.github.wulkanowy.sdk.scrapper.messages.Recipient import io.github.wulkanowy.sdk.scrapper.messages.RecipientType import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import org.jsoup.Jsoup import org.slf4j.LoggerFactory @@ -201,7 +202,7 @@ internal fun String.md5(): String { return digest.toHexString() } -internal fun HttpUrl.mapModuleUrls(moduleHost: String, appVersion: String?): HttpUrl { +internal fun HttpUrl.mapModuleUrl(moduleHost: String, appVersion: String?): HttpUrl { val pathSegmentIndex = getPathIndexByModuleHost(moduleHost) val pathKey = pathSegments.getOrNull(pathSegmentIndex) val mappedPath = Scrapper.endpointsMap[appVersion] @@ -221,6 +222,20 @@ internal fun HttpUrl.mapModuleUrls(moduleHost: String, appVersion: String?): Htt } } +internal fun isAnyMappingAvailable(url: String): Boolean { + val host = url.toHttpUrl().host + val module = when { + MessagesModuleHost in host -> MessagesModuleHost + StudentPlusModuleHost in host -> StudentPlusModuleHost + StudentModuleHost in host -> StudentModuleHost + else -> null + } ?: return false + + return Scrapper.endpointsMap.keys.any { appVersion -> + url.toHttpUrl().mapModuleUrl(module, appVersion).toString() !== url + } +} + internal fun getPathIndexByModuleHost(moduleHost: String): Int = when (moduleHost) { StudentPlusModuleHost -> 3 StudentModuleHost, MessagesModuleHost -> 2 diff --git a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/exception/VulcanServerError.kt b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/exception/VulcanServerError.kt new file mode 100644 index 00000000..b2c16b35 --- /dev/null +++ b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/exception/VulcanServerError.kt @@ -0,0 +1,9 @@ +package io.github.wulkanowy.sdk.scrapper.exception + +import org.jsoup.nodes.Document + +class VulcanServerError internal constructor( + message: String, + val doc: Document, + val httpCode: Int, +) : VulcanException(message) diff --git a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/AutoLoginInterceptor.kt b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/AutoLoginInterceptor.kt index da3ff8a1..8c1f1370 100644 --- a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/AutoLoginInterceptor.kt +++ b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/AutoLoginInterceptor.kt @@ -11,12 +11,14 @@ import io.github.wulkanowy.sdk.scrapper.Scrapper.LoginType.ADFSLightScoped import io.github.wulkanowy.sdk.scrapper.Scrapper.LoginType.STANDARD import io.github.wulkanowy.sdk.scrapper.attachVToken import io.github.wulkanowy.sdk.scrapper.exception.VulcanClientError +import io.github.wulkanowy.sdk.scrapper.exception.VulcanServerError import io.github.wulkanowy.sdk.scrapper.getModuleHeadersFromDocument +import io.github.wulkanowy.sdk.scrapper.isAnyMappingAvailable import io.github.wulkanowy.sdk.scrapper.login.LoginResult import io.github.wulkanowy.sdk.scrapper.login.ModuleHeaders import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException import io.github.wulkanowy.sdk.scrapper.login.UrlGenerator -import io.github.wulkanowy.sdk.scrapper.mapModuleUrls +import io.github.wulkanowy.sdk.scrapper.mapModuleUrl import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_CARDS import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_LIGHT @@ -71,8 +73,9 @@ internal class AutoLoginInterceptor( val response = try { chain.proceed(request.attachModuleHeaders()) } catch (e: Throwable) { - if (e is VulcanClientError) { - checkHttpErrorResponse(e, url) + when (e) { + is VulcanClientError -> checkHttpErrorResponse(e, url) + is VulcanServerError -> checkServerError(e, url) } throw e } @@ -163,7 +166,7 @@ internal class AutoLoginInterceptor( } val headers = headersByHost[moduleHost] - val mappedUrl = url.mapModuleUrls(moduleHost, headers?.appVersion) + val mappedUrl = url.mapModuleUrl(moduleHost, headers?.appVersion) logger.info("X-V-AppVersion: ${headers?.appVersion}") @@ -214,18 +217,27 @@ internal class AutoLoginInterceptor( // new error style val isCodeMatch = response.code == HttpURLConnection.HTTP_OK val isJsonContent = bodyContent.startsWith("{") - val isSubdomainMatch = "uonetplus-uczen" in url - if (isCodeMatch && isJsonContent && isSubdomainMatch) { - runCatching { json.decodeFromString>(bodyContent) } - .onFailure { logger.error("AutoLoginInterceptor: Can't deserialize new style error content body", it) } - .onSuccess { - it.feedback?.message?.let { errorMessage -> - if ("Brak uprawnień" in errorMessage) { - throw NotLoggedInException(errorMessage) - } + val isStudentModuleSubdomain = StudentModuleHost in url + if (isCodeMatch && isJsonContent && isStudentModuleSubdomain) { + checkResponseStudentModule(bodyContent) + } + } + + private fun checkResponseStudentModule(bodyContent: String) { + runCatching { json.decodeFromString>(bodyContent) } + .onFailure { logger.error("AutoLoginInterceptor: Can't deserialize new style error content body", it) } + .onSuccess { + it.feedback?.message?.let { errorMessage -> + if ("Brak uprawnień" in errorMessage) { + throw NotLoggedInException(errorMessage) + } + + // workaround - access resource before request mapping + if ("was not found on controller" in errorMessage && headersByHost[StudentModuleHost] == null) { + throw NotLoggedInException(errorMessage) } } - } + } } private fun checkHttpErrorResponse(error: VulcanClientError, url: String) { @@ -236,6 +248,17 @@ internal class AutoLoginInterceptor( } } + private fun checkServerError(error: VulcanServerError, url: String) { + val isCodeMatch = error.httpCode == HttpURLConnection.HTTP_OK + val isSubdomainMatch = MessagesModuleHost in url || StudentModuleHost in url + val isMappable = isAnyMappingAvailable(url) + + // workaround - access resource before request mapping + if (isCodeMatch && isSubdomainMatch && isMappable) { + throw NotLoggedInException(error.message.orEmpty()) + } + } + /** * @see [https://github.com/square/retrofit/issues/3110#issuecomment-536248102] */ diff --git a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/ErrorInterceptor.kt b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/ErrorInterceptor.kt index f4e0b9f0..055dbc47 100644 --- a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/ErrorInterceptor.kt +++ b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/interceptor/ErrorInterceptor.kt @@ -7,6 +7,7 @@ import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException import io.github.wulkanowy.sdk.scrapper.exception.TemporarilyDisabledException import io.github.wulkanowy.sdk.scrapper.exception.VulcanException +import io.github.wulkanowy.sdk.scrapper.exception.VulcanServerError import io.github.wulkanowy.sdk.scrapper.login.AccountPermissionException import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException @@ -91,7 +92,7 @@ internal class ErrorInterceptor( } when (doc.title()) { - "Błąd" -> throw VulcanException(doc.body().text(), httpCode) + "Błąd" -> throw VulcanServerError(doc.body().text(), doc, httpCode) "Błąd strony" -> throw VulcanException(doc.select(".errorMessage").text(), httpCode) "Logowanie" -> throw AccountPermissionException( buildString { diff --git a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/repository/RegisterRepository.kt b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/repository/RegisterRepository.kt index 2a93ee66..a6574ef6 100644 --- a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/repository/RegisterRepository.kt +++ b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/repository/RegisterRepository.kt @@ -16,7 +16,7 @@ import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.sdk.scrapper.login.LoginHelper import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException import io.github.wulkanowy.sdk.scrapper.login.UrlGenerator -import io.github.wulkanowy.sdk.scrapper.mapModuleUrls +import io.github.wulkanowy.sdk.scrapper.mapModuleUrl import io.github.wulkanowy.sdk.scrapper.register.Diary import io.github.wulkanowy.sdk.scrapper.register.HomePageResponse import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent @@ -251,7 +251,7 @@ internal class RegisterRepository( } val diaryUrl = (url.generate(UrlGenerator.Site.STUDENT) + "UczenDziennik.mvc/Get") .toHttpUrl() - .mapModuleUrls(StudentModuleHost, appVersion) + .mapModuleUrl(StudentModuleHost, appVersion) return student .getSchoolInfo(url = diaryUrl.toString()) @@ -330,7 +330,7 @@ internal class RegisterRepository( } val cacheUrl = (url.generate(UrlGenerator.Site.STUDENT) + "UczenCache.mvc/Get") .toHttpUrl() - .mapModuleUrls(StudentModuleHost, appVersion) + .mapModuleUrl(StudentModuleHost, appVersion) val userCache = student.getUserCache( url = cacheUrl.toString(), @@ -347,11 +347,11 @@ internal class RegisterRepository( val contextUrl = (baseStudentPlus + "api/Context").toHttpUrl() val contextVToken = contextUrl.getMatchedVToken(StudentPlusModuleHost, moduleHeaders) - val mappedContextUrl = contextUrl.mapModuleUrls(StudentPlusModuleHost, moduleHeaders.appVersion) + val mappedContextUrl = contextUrl.mapModuleUrl(StudentPlusModuleHost, moduleHeaders.appVersion) val semestersUrl = (baseStudentPlus + "api/OkresyKlasyfikacyjne").toHttpUrl() val semestersVToken = semestersUrl.getMatchedVToken(StudentPlusModuleHost, moduleHeaders) - val mappedSemestersUrl = semestersUrl.mapModuleUrls(StudentPlusModuleHost, moduleHeaders.appVersion) + val mappedSemestersUrl = semestersUrl.mapModuleUrl(StudentPlusModuleHost, moduleHeaders.appVersion) return studentPlus .getContextByUrl(vToken = contextVToken, url = mappedContextUrl.toString()).students diff --git a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/service/ServiceManager.kt b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/service/ServiceManager.kt index d59c06fa..b71e46e8 100644 --- a/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/service/ServiceManager.kt +++ b/sdk-scrapper/src/main/kotlin/io/github/wulkanowy/sdk/scrapper/service/ServiceManager.kt @@ -102,7 +102,6 @@ internal class ServiceManager( private val interceptors: MutableList> = mutableListOf( HttpLoggingInterceptor().setLevel(logLevel) to true, - ErrorInterceptor(cookieJarCabinet) to false, AutoLoginInterceptor( loginType = loginType, cookieJarCabinet = cookieJarCabinet, @@ -113,6 +112,7 @@ internal class ServiceManager( headersByHost = headersByHost, loginLock = loginLock, ) to false, + ErrorInterceptor(cookieJarCabinet) to false, UserAgentInterceptor(androidVersion, buildTag, userAgentTemplate) to false, HttpErrorInterceptor() to false, )