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 1905c733..8369644a 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 @@ -168,6 +168,7 @@ const val ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED = 349 const val ERROR_VULCAN_WEB_LOGGED_OUT = 350 const val ERROR_VULCAN_WEB_CERTIFICATE_POST_FAILED = 351 const val ERROR_VULCAN_WEB_GRADUATE_ACCOUNT = 352 +const val ERROR_VULCAN_WEB_NO_SCHOOLS = 353 const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401 const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index 5da87c73..730933cb 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -140,7 +140,10 @@ object Regexes { """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() } val VULCAN_WEB_PERMISSIONS by lazy { - """permissions: '([A-z0-9\/=+\-_]+?)'""".toRegex() + """permissions: '([A-z0-9/=+\-_]+?)'""".toRegex() + } + val VULCAN_WEB_SYMBOL_VALIDATE by lazy { + """[A-z0-9]+""".toRegex(IGNORE_CASE) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt index fc11d73d..14adcbe3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt @@ -17,9 +17,10 @@ import pl.szczodrzynski.edziennik.values class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { - fun isWebMainLoginValid() = currentSemesterEndDate-30 > currentTimeUnix() - && apiFingerprint[symbol].isNotNullNorEmpty() - && apiPrivateKey[symbol].isNotNullNorEmpty() + fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix() + && webAuthCookie.isNotNullNorEmpty() + && webHost.isNotNullNorEmpty() + && webType.isNotNullNorEmpty() && symbol.isNotNullNorEmpty() fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix() && apiFingerprint[symbol].isNotNullNorEmpty() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt index d1f11822..ab032e0c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/VulcanFeatures.kt @@ -19,6 +19,7 @@ const val ENDPOINT_VULCAN_API_NOTICES = 1070 const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080 const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090 const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100 +const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010 val VulcanFeatures = listOf( // timetable @@ -61,6 +62,13 @@ val VulcanFeatures = listOf( !data.app.config.sync.tokenVulcanList.contains(data.profileId) }, + /** + * Lucky number - using WEB Main. + */ + Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN + ), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)).withShouldSync { data -> data.shouldSyncLuckyNumber() }, + Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf( ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API, ENDPOINT_VULCAN_API_DICTIONARIES to LOGIN_METHOD_VULCAN_API diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt index 9ab94c30..491e8ea6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanData.kt @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.* import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber import pl.szczodrzynski.edziennik.utils.Utils class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) { @@ -86,6 +87,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) VulcanApiMessagesSent(data, lastSync, onSuccess) } + ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + VulcanWebLuckyNumber(data, lastSync, onSuccess) + } else -> onSuccess(endpointId) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt index a2c0247a..39433657 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/VulcanWebMain.kt @@ -44,9 +44,9 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { Jspoon.create().adapter(CufsCertificate::class.java) } - fun saveCertificate(certificate: String) { + fun saveCertificate(xml: String) { val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml") - file.writeText(certificate) + file.writeText(xml) } fun readCertificate(): String? { @@ -56,20 +56,20 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { return null } - fun parseCertificate(certificate: String): CufsCertificate { - val xml = certificate + fun parseCertificate(xml: String): CufsCertificate { + val xmlParsed = xml .replace("<[a-z]+?:".toRegex(), "<") .replace(" Unit): Boolean { - val cufsCertificate = parseCertificate(certificate) - + fun postCertificate(certificate: CufsCertificate, symbol: String, onResult: (symbol: String, state: Int) -> Unit): Boolean { // check if the certificate is valid - if (Date.fromIso(cufsCertificate.expiryDate) < System.currentTimeMillis()) + if (Date.fromIso(certificate.expiryDate) < System.currentTimeMillis()) return false val callback = object : TextCallbackHandler() { @@ -86,6 +86,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { if (!validateCallback(text, response, jsonResponse = false)) { return } + data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L onResult(symbol, STATE_SUCCESS) } @@ -102,8 +103,8 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { .userAgent(SYSTEM_USER_AGENT) .post() .addParameter("wa", "wsignin1.0") - .addParameter("wctx", cufsCertificate.targetUrl) - .addParameter("wresult", certificate) + .addParameter("wctx", certificate.targetUrl) + .addParameter("wresult", certificate.xml) .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN) .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) @@ -138,7 +139,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] } val schoolSymbols = mutableListOf() - val clientUrl = "https://uonetplus-uczen.${data.webHost}/$symbol/" + val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/" var clientIndex = text.indexOf(clientUrl) var count = 0 while (clientIndex != -1 && count < 100) { @@ -149,6 +150,17 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { clientIndex = text.indexOf(clientUrl, startIndex = endIndex) count++ } + schoolSymbols.removeAll { + it.toLowerCase() == "default" + || !it.matches(Regexes.VULCAN_WEB_SYMBOL_VALIDATE) + } + + if (postErrors && schoolSymbols.isEmpty()) { + data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_SCHOOLS) + .withResponse(response) + .withApiResponse(text)) + return + } onSuccess(text, schoolSymbols) } @@ -209,7 +221,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) { webType: Int, endpoint: String, method: Int = POST, - parameters: Map = emptyMap(), + parameters: Map = emptyMap(), onSuccess: (json: JsonObject, response: Response?) -> Unit ) { val url = "https://" + when (webType) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt new file mode 100644 index 00000000..4d657ac7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/HomepageTile.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web + +import com.google.gson.annotations.SerializedName + +data class HomepageTile( + @SerializedName("Nazwa") + val name: String?, + @SerializedName("Url") + val url: String?, + @SerializedName("Zawartosc") + val children: List +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt new file mode 100644 index 00000000..dbdbc599 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/web/VulcanWebLuckyNumber.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-4-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.data.api.VULCAN_WEB_ENDPOINT_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonArray +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class VulcanWebLuckyNumber(override val data: DataVulcan, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : VulcanWebMain(data, lastSync) { + companion object { + const val TAG = "VulcanWebLuckyNumber" + } + + init { + webGetJson(TAG, WEB_MAIN, VULCAN_WEB_ENDPOINT_LUCKY_NUMBER, parameters = mapOf( + "permissions" to data.webPermissions + )) { json, _ -> + val tiles = json + .getJsonArray("data") + ?.mapNotNull { data.app.gson.fromJson(it.toString(), HomepageTile::class.java) } + ?.flatMap { it.children } + + if (tiles == null) { + data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS) + return@webGetJson + } + + var nextSync = System.currentTimeMillis() + 1* DAY *1000 + + tiles.firstOrNull { it.name == data.schoolShort }?.children?.firstOrNull()?.let { tile -> + // "Szczęśliwy numer w dzienniku: 16" + return@let tile.name?.substringAfterLast(' ')?.toIntOrNull()?.let { number -> + // lucky number present + val luckyNumberObject = LuckyNumber( + profileId, + Date.getToday(), + number + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile?.empty ?: false, + System.currentTimeMillis() + )) + } + } ?: { + // no lucky number + if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour >= 22) { + // working days, after 10PM + // consider the lucky number is disabled; sync in 4 days + nextSync = System.currentTimeMillis() + 4*DAY*1000 + } + else if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 22) { + // working days, before 10PM + + } + else { + // weekends + nextSync = Week.getNearestWeekDayDate(Week.MONDAY).combineWith(Time(5, 0, 0)) + } + }() + + data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS) + onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt index 3d7e3aea..00eb7f24 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/firstlogin/VulcanFirstLogin.kt @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.* import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent @@ -32,17 +33,18 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { init { if (data.loginStore.mode == LOGIN_MODE_VULCAN_WEB) { VulcanLoginWebMain(data) { - val certificate = web.readCertificate() ?: run { + val xml = web.readCertificate() ?: run { data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_CERTIFICATE)) return@VulcanLoginWebMain } + val certificate = web.parseCertificate(xml) if (data.symbol != null && data.symbol != "default") { tryingSymbols += data.symbol ?: "default" } else { - val cufsCertificate = web.parseCertificate(certificate) - tryingSymbols += cufsCertificate.userInstances + + tryingSymbols += certificate.userInstances } checkSymbol(certificate) @@ -56,7 +58,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { } } - private fun checkSymbol(certificate: String) { + private fun checkSymbol(certificate: CufsCertificate) { if (tryingSymbols.isEmpty()) { EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore)) onSuccess() @@ -80,12 +82,16 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) { // postCertificate returns false if the cert is not valid anymore if (!result) { data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED) - .withApiResponse(certificate)) + .withApiResponse(certificate.xml)) } } private fun webRegisterDevice(symbol: String, onSuccess: () -> Unit) { web.getStartPage(symbol, postErrors = false) { _, schoolSymbols -> + if (schoolSymbols.isEmpty()) { + onSuccess() + return@getStartPage + } data.symbol = symbol val schoolSymbol = data.schoolSymbol ?: schoolSymbols.firstOrNull() web.webGetJson(TAG, VulcanWebMain.WEB_NEW, "$schoolSymbol/$VULCAN_WEB_ENDPOINT_REGISTER_DEVICE") { result, _ -> diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt index 5e2e299f..45fc96d2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/CufsCertificate.kt @@ -18,4 +18,6 @@ class CufsCertificate { @Selector(value = "Attribute[AttributeName=UserInstance] AttributeValue") var userInstances: List = listOf() + + var xml = "" } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt index 04a6f17e..467beea3 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/login/VulcanLoginWebMain.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.fslogin.FSLogin import pl.szczodrzynski.fslogin.realm.CufsRealm @@ -82,35 +83,20 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) { else -> return false } + val certificate = web.readCertificate()?.let { web.parseCertificate(it) } + if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) { + useCertificate(certificate) + return true + } + val fsLogin = FSLogin(data.app.http, debug = App.debugMode) fsLogin.performLogin( realm = realm, username = data.webUsername ?: data.webEmail ?: return false, password = data.webPassword ?: return false, - onSuccess = { certificate -> - web.saveCertificate(certificate.wresult) - - // auto-post certificate when not first login - if (data.profile != null && data.symbol != null && data.symbol != "default") { - val result = web.postCertificate(certificate.wresult, data.symbol ?: "default") { _, state -> - when (state) { - VulcanWebMain.STATE_SUCCESS -> { - web.getStartPage { _, _ -> onSuccess() } - } - VulcanWebMain.STATE_NO_REGISTER -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_REGISTER)) - VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT)) - } - } - // postCertificate returns false if the cert is not valid anymore - if (!result) { - data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED) - .withApiResponse(certificate.wresult)) - } - } - else { - // first login - succeed immediately - onSuccess() - } + onSuccess = { fsCertificate -> + web.saveCertificate(fsCertificate.wresult) + useCertificate(web.parseCertificate(fsCertificate.wresult)) }, onFailure = { errorText -> // TODO @@ -120,4 +106,28 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) { return true } + + private fun useCertificate(certificate: CufsCertificate) { + // auto-post certificate when not first login + if (data.profile != null && data.symbol != null && data.symbol != "default") { + val result = web.postCertificate(certificate, data.symbol ?: "default") { _, state -> + when (state) { + VulcanWebMain.STATE_SUCCESS -> { + web.getStartPage { _, _ -> onSuccess() } + } + VulcanWebMain.STATE_NO_REGISTER -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_REGISTER)) + VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT)) + } + } + // postCertificate returns false if the cert is not valid anymore + if (!result) { + data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED) + .withApiResponse(certificate.xml)) + } + } + else { + // first login - succeed immediately + onSuccess() + } + } }