Add workarounds to detect not mapped urls as NotLoggedIn errors in multi threaded envs

This commit is contained in:
Mikołaj Pich 2024-05-12 17:32:29 +02:00
parent ce84113020
commit 6fff07e52a
No known key found for this signature in database
7 changed files with 75 additions and 22 deletions

View file

@ -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 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 final class io/github/wulkanowy/sdk/scrapper/grades/Grade {
public static final field Companion Lio/github/wulkanowy/sdk/scrapper/grades/Grade$Companion; public static final field Companion Lio/github/wulkanowy/sdk/scrapper/grades/Grade$Companion;
public field date Ljava/time/LocalDate; public field date Ljava/time/LocalDate;

View file

@ -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.Recipient
import io.github.wulkanowy.sdk.scrapper.messages.RecipientType import io.github.wulkanowy.sdk.scrapper.messages.RecipientType
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -201,7 +202,7 @@ internal fun String.md5(): String {
return digest.toHexString() 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 pathSegmentIndex = getPathIndexByModuleHost(moduleHost)
val pathKey = pathSegments.getOrNull(pathSegmentIndex) val pathKey = pathSegments.getOrNull(pathSegmentIndex)
val mappedPath = Scrapper.endpointsMap[appVersion] 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) { internal fun getPathIndexByModuleHost(moduleHost: String): Int = when (moduleHost) {
StudentPlusModuleHost -> 3 StudentPlusModuleHost -> 3
StudentModuleHost, MessagesModuleHost -> 2 StudentModuleHost, MessagesModuleHost -> 2

View file

@ -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)

View file

@ -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.Scrapper.LoginType.STANDARD
import io.github.wulkanowy.sdk.scrapper.attachVToken import io.github.wulkanowy.sdk.scrapper.attachVToken
import io.github.wulkanowy.sdk.scrapper.exception.VulcanClientError 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.getModuleHeadersFromDocument
import io.github.wulkanowy.sdk.scrapper.isAnyMappingAvailable
import io.github.wulkanowy.sdk.scrapper.login.LoginResult import io.github.wulkanowy.sdk.scrapper.login.LoginResult
import io.github.wulkanowy.sdk.scrapper.login.ModuleHeaders import io.github.wulkanowy.sdk.scrapper.login.ModuleHeaders
import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException
import io.github.wulkanowy.sdk.scrapper.login.UrlGenerator 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
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_CARDS import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_CARDS
import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_LIGHT import io.github.wulkanowy.sdk.scrapper.repository.AccountRepository.Companion.SELECTOR_ADFS_LIGHT
@ -71,8 +73,9 @@ internal class AutoLoginInterceptor(
val response = try { val response = try {
chain.proceed(request.attachModuleHeaders()) chain.proceed(request.attachModuleHeaders())
} catch (e: Throwable) { } catch (e: Throwable) {
if (e is VulcanClientError) { when (e) {
checkHttpErrorResponse(e, url) is VulcanClientError -> checkHttpErrorResponse(e, url)
is VulcanServerError -> checkServerError(e, url)
} }
throw e throw e
} }
@ -163,7 +166,7 @@ internal class AutoLoginInterceptor(
} }
val headers = headersByHost[moduleHost] 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}") logger.info("X-V-AppVersion: ${headers?.appVersion}")
@ -214,8 +217,13 @@ internal class AutoLoginInterceptor(
// new error style // new error style
val isCodeMatch = response.code == HttpURLConnection.HTTP_OK val isCodeMatch = response.code == HttpURLConnection.HTTP_OK
val isJsonContent = bodyContent.startsWith("{") val isJsonContent = bodyContent.startsWith("{")
val isSubdomainMatch = "uonetplus-uczen" in url val isStudentModuleSubdomain = StudentModuleHost in url
if (isCodeMatch && isJsonContent && isSubdomainMatch) { if (isCodeMatch && isJsonContent && isStudentModuleSubdomain) {
checkResponseStudentModule(bodyContent)
}
}
private fun checkResponseStudentModule(bodyContent: String) {
runCatching { json.decodeFromString<ApiResponse<Unit?>>(bodyContent) } runCatching { json.decodeFromString<ApiResponse<Unit?>>(bodyContent) }
.onFailure { logger.error("AutoLoginInterceptor: Can't deserialize new style error content body", it) } .onFailure { logger.error("AutoLoginInterceptor: Can't deserialize new style error content body", it) }
.onSuccess { .onSuccess {
@ -223,6 +231,10 @@ internal class AutoLoginInterceptor(
if ("Brak uprawnień" in errorMessage) { if ("Brak uprawnień" in errorMessage) {
throw NotLoggedInException(errorMessage) throw NotLoggedInException(errorMessage)
} }
// workaround - access resource before request mapping
if ("was not found on controller" in errorMessage && headersByHost[StudentModuleHost] == null) {
throw NotLoggedInException(errorMessage)
} }
} }
} }
@ -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] * @see [https://github.com/square/retrofit/issues/3110#issuecomment-536248102]
*/ */

View file

@ -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.ServiceUnavailableException
import io.github.wulkanowy.sdk.scrapper.exception.TemporarilyDisabledException import io.github.wulkanowy.sdk.scrapper.exception.TemporarilyDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.VulcanException 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.AccountPermissionException
import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException import io.github.wulkanowy.sdk.scrapper.login.InvalidSymbolException
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
@ -91,7 +92,7 @@ internal class ErrorInterceptor(
} }
when (doc.title()) { 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) "Błąd strony" -> throw VulcanException(doc.select(".errorMessage").text(), httpCode)
"Logowanie" -> throw AccountPermissionException( "Logowanie" -> throw AccountPermissionException(
buildString { buildString {

View file

@ -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.LoginHelper
import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException import io.github.wulkanowy.sdk.scrapper.login.NotLoggedInException
import io.github.wulkanowy.sdk.scrapper.login.UrlGenerator 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.Diary
import io.github.wulkanowy.sdk.scrapper.register.HomePageResponse import io.github.wulkanowy.sdk.scrapper.register.HomePageResponse
import io.github.wulkanowy.sdk.scrapper.register.RegisterStudent 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") val diaryUrl = (url.generate(UrlGenerator.Site.STUDENT) + "UczenDziennik.mvc/Get")
.toHttpUrl() .toHttpUrl()
.mapModuleUrls(StudentModuleHost, appVersion) .mapModuleUrl(StudentModuleHost, appVersion)
return student return student
.getSchoolInfo(url = diaryUrl.toString()) .getSchoolInfo(url = diaryUrl.toString())
@ -330,7 +330,7 @@ internal class RegisterRepository(
} }
val cacheUrl = (url.generate(UrlGenerator.Site.STUDENT) + "UczenCache.mvc/Get") val cacheUrl = (url.generate(UrlGenerator.Site.STUDENT) + "UczenCache.mvc/Get")
.toHttpUrl() .toHttpUrl()
.mapModuleUrls(StudentModuleHost, appVersion) .mapModuleUrl(StudentModuleHost, appVersion)
val userCache = student.getUserCache( val userCache = student.getUserCache(
url = cacheUrl.toString(), url = cacheUrl.toString(),
@ -347,11 +347,11 @@ internal class RegisterRepository(
val contextUrl = (baseStudentPlus + "api/Context").toHttpUrl() val contextUrl = (baseStudentPlus + "api/Context").toHttpUrl()
val contextVToken = contextUrl.getMatchedVToken(StudentPlusModuleHost, moduleHeaders) 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 semestersUrl = (baseStudentPlus + "api/OkresyKlasyfikacyjne").toHttpUrl()
val semestersVToken = semestersUrl.getMatchedVToken(StudentPlusModuleHost, moduleHeaders) val semestersVToken = semestersUrl.getMatchedVToken(StudentPlusModuleHost, moduleHeaders)
val mappedSemestersUrl = semestersUrl.mapModuleUrls(StudentPlusModuleHost, moduleHeaders.appVersion) val mappedSemestersUrl = semestersUrl.mapModuleUrl(StudentPlusModuleHost, moduleHeaders.appVersion)
return studentPlus return studentPlus
.getContextByUrl(vToken = contextVToken, url = mappedContextUrl.toString()).students .getContextByUrl(vToken = contextVToken, url = mappedContextUrl.toString()).students

View file

@ -102,7 +102,6 @@ internal class ServiceManager(
private val interceptors: MutableList<Pair<Interceptor, Boolean>> = mutableListOf( private val interceptors: MutableList<Pair<Interceptor, Boolean>> = mutableListOf(
HttpLoggingInterceptor().setLevel(logLevel) to true, HttpLoggingInterceptor().setLevel(logLevel) to true,
ErrorInterceptor(cookieJarCabinet) to false,
AutoLoginInterceptor( AutoLoginInterceptor(
loginType = loginType, loginType = loginType,
cookieJarCabinet = cookieJarCabinet, cookieJarCabinet = cookieJarCabinet,
@ -113,6 +112,7 @@ internal class ServiceManager(
headersByHost = headersByHost, headersByHost = headersByHost,
loginLock = loginLock, loginLock = loginLock,
) to false, ) to false,
ErrorInterceptor(cookieJarCabinet) to false,
UserAgentInterceptor(androidVersion, buildTag, userAgentTemplate) to false, UserAgentInterceptor(androidVersion, buildTag, userAgentTemplate) to false,
HttpErrorInterceptor() to false, HttpErrorInterceptor() to false,
) )