diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt index 0b2ceefc..0f8c0d35 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiService.kt @@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.api.v2.events.* import pl.szczodrzynski.edziennik.api.v2.events.requests.* import pl.szczodrzynski.edziennik.api.v2.events.task.ErrorReportTask import pl.szczodrzynski.edziennik.api.v2.events.task.NotifyTask +import pl.szczodrzynski.edziennik.api.v2.idziennik.Idziennik import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.api.v2.librus.Librus @@ -219,6 +220,7 @@ class ApiService : Service() { LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback) + LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback) LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) else -> null } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt index 371e3c58..90aa98ec 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt @@ -45,7 +45,18 @@ const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/to const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module" const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action=" - +const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT +const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik" +const val IDZIENNIK_WEB_LOGIN = "login.aspx" +const val IDZIENNIK_WEB_SETTINGS = "mod_panelRodzica/Ustawienia.aspx" +const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec" +const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia" +const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia" +const val IDZIENNIK_WEB_EXAMS = "mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe" +const val IDZIENNIK_WEB_NOTICES = "mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia" +const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia" +const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia" +const val IDZIENNIK_WEB_MESSAGES_LIST = "mod_komunikator/WS_wiadomosci.asmx/PobierzListeWiadomosci" val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt index a16c265e..6dd2ffb9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/LoginMethods.kt @@ -4,9 +4,11 @@ package pl.szczodrzynski.edziennik.api.v2 -import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal +import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginApi +import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginWeb import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages +import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod @@ -118,6 +120,16 @@ val vulcanLoginMethods = listOf( } ) +val idziennikLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_WEB, IdziennikLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_API, IdziennikLoginApi::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB } +) + val templateLoginMethods = listOf( LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java) .withIsPossible { _, _ -> true } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt index ab0f4260..a35d95ba 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt @@ -36,4 +36,28 @@ object Regexes { val MOBIDZIENNIK_CLASS_CALENDAR by lazy { """events: (.+),$""".toRegex(RegexOption.MULTILINE) } + + + + val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { + "".toRegex(RegexOption.DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_ERROR by lazy { + "id=\"spanErrorMessage\">(.*?)(.+?)".toRegex(RegexOption.DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy { + "id=\"ctl00_CzyRodzic\" value=\"([01])\" />".toRegex() + } + val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy { + "name=\"ctl00\\\$dxComboRokSzkolny\".+?selected=\"selected\".*?value=\"([0-9]+)\">([0-9/]+)<".toRegex(RegexOption.DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy { + "".toRegex(RegexOption.DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { + "(.+?)\\s(.+?)\\s*\\((.+?),\\s*(.+?)\\)".toRegex(RegexOption.DOT_MATCHES_ALL) + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt new file mode 100644 index 00000000..faa2f517 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik + +import okhttp3.Cookie +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_API +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_WEB +import pl.szczodrzynski.edziennik.api.v2.models.Data +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore +import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.isNotNullNorEmpty + +class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isWebLoginValid() = loginExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() && webAuth.isNotNullNorEmpty() + fun isApiLoginValid() = loginExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isWebLoginValid()) { + loginMethods += LOGIN_METHOD_IDZIENNIK_WEB + app.cookieJar.saveFromResponse(null, listOf( + Cookie.Builder() + .name("ASP.NET_SessionId_iDziennik") + .value(webSessionId!!) + .domain("iuczniowie.progman.pl") + .secure().httpOnly().build(), + Cookie.Builder() + .name(".ASPXAUTH") + .value(webAuth!!) + .domain("iuczniowie.progman.pl") + .secure().httpOnly().build() + )) + } + if (isApiLoginValid()) + loginMethods += LOGIN_METHOD_IDZIENNIK_API + } + + private var mLoginExpiryTime: Long? = null + var loginExpiryTime: Long + get() { mLoginExpiryTime = mLoginExpiryTime ?: loginStore.getLoginData("loginExpiryTime", 0L); return mLoginExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("loginExpiryTime", value); mLoginExpiryTime = value } + + /* __ __ _ + \ \ / / | | + \ \ /\ / /__| |__ + \ \/ \/ / _ \ '_ \ + \ /\ / __/ |_) | + \/ \/ \___|_._*/ + private var mWebSchoolName: String? = null + var webSchoolName: String? + get() { mWebSchoolName = mWebSchoolName ?: loginStore.getLoginData("schoolName", null); return mWebSchoolName } + set(value) { loginStore.putLoginData("schoolName", value); mWebSchoolName = value } + private var mWebUsername: String? = null + var webUsername: String? + get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("username", null); return mWebUsername } + set(value) { loginStore.putLoginData("username", value); mWebUsername = value } + private var mWebPassword: String? = null + var webPassword: String? + get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("password", null); return mWebPassword } + set(value) { loginStore.putLoginData("password", value); mWebPassword = value } + + private var mWebSessionId: String? = null + var webSessionId: String? + get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId } + set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value } + private var mWebAuth: String? = null + var webAuth: String? + get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth } + set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value } + + /* _ + /\ (_) + / \ _ __ _ + / /\ \ | '_ \| | + / ____ \| |_) | | + /_/ \_\ .__/|_| + | | + |*/ + private var mApiBearer: String? = null + var apiBearer: String? + get() { mApiBearer = mApiBearer ?: loginStore.getLoginData("apiBearer", null); return mApiBearer } + set(value) { loginStore.putLoginData("apiBearer", value); mApiBearer = value } + + /* ____ _ _ + / __ \| | | | + | | | | |_| |__ ___ _ __ + | | | | __| '_ \ / _ \ '__| + | |__| | |_| | | | __/ | + \____/ \__|_| |_|\___|*/ + private var mStudentId: String? = null + var studentId: String? + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId } + set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + + private var mRegisterId: Int? = null + var registerId: Int + get() { mRegisterId = mRegisterId ?: profile?.getStudentData("registerId", 0); return mRegisterId ?: 0 } + set(value) { profile?.putStudentData("registerId", value) ?: return; mRegisterId = value } + + private var mSchoolYearId: Int? = null + var schoolYearId: Int + get() { mSchoolYearId = mSchoolYearId ?: profile?.getStudentData("schoolYearId", 0); return mSchoolYearId ?: 0 } + set(value) { profile?.putStudentData("schoolYearId", value) ?: return; mSchoolYearId = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/Idziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/Idziennik.kt new file mode 100644 index 00000000..2a09756f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/Idziennik.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik + +import android.util.Log +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 +import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikData +import pl.szczodrzynski.edziennik.api.v2.idziennik.firstlogin.IdziennikFirstLogin +import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLogin +import pl.szczodrzynski.edziennik.api.v2.idziennikLoginMethods +import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.api.v2.prepare +import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore +import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.utils.Utils + +class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Idziennik" + } + + val internalErrorList = mutableListOf() + val data: DataIdziennik + + init { + data = DataIdziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Idziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + data.notifyAndSyncEvents { + callback.onCompleted() + } + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?) { + data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId) + Log.d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}") + Log.d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + IdziennikLogin(data) { + IdziennikData(data) { + completed() + } + } + } + + override fun getMessage(messageId: Int) { + + } + + override fun markAllAnnouncementsAsRead() { + + } + + override fun firstLogin() { + IdziennikFirstLogin(data) { + completed() + } + } + + override fun cancel() { + Utils.d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { + callback.onCompleted() + } + + override fun onProgress(step: Int) { + callback.onProgress(step) + } + + override fun onStartProgress(stringRes: Int) { + callback.onStartProgress(stringRes) + } + + override fun onError(apiError: ApiError) { + when (apiError.errorCode) { + in internalErrorList -> { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + } + CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { + internalErrorList.add(apiError.errorCode) + loginStore.removeLoginData("refreshToken") // force a clean login + //loginLibrus() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/IdziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/IdziennikFeatures.kt new file mode 100644 index 00000000..c3a01ea1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/IdziennikFeatures.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik + +import pl.szczodrzynski.edziennik.api.v2.* +import pl.szczodrzynski.edziennik.api.v2.models.Feature + +const val ENDPOINT_IDZIENNIK_WEB_SAMPLE = 9991 +const val ENDPOINT_IDZIENNIK_WEB_SAMPLE_2 = 9992 +const val ENDPOINT_IDZIENNIK_API_SAMPLE = 9993 + +val IdziennikFeatures = listOf( + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_STUDENT_INFO, listOf( + ENDPOINT_IDZIENNIK_WEB_SAMPLE to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_SCHOOL_INFO, listOf( + ENDPOINT_IDZIENNIK_WEB_SAMPLE_2 to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_GRADES, listOf( + ENDPOINT_IDZIENNIK_API_SAMPLE to LOGIN_METHOD_IDZIENNIK_API + ), listOf(LOGIN_METHOD_IDZIENNIK_API)) +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt new file mode 100644 index 00000000..4873a275 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.data + +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.utils.Utils + +class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikData" + } + + private var cancelled = false + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useEndpoint(data.targetEndpointIds.removeAt(0)) { + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId") + when (endpointId) { + /*ENDPOINT_IDZIENNIK_WEB_SAMPLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + IdziennikWebSample(data) { onSuccess() } + }*/ + else -> onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikWeb.kt new file mode 100644 index 00000000..f1241c0c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikWeb.kt @@ -0,0 +1,147 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.data + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.JsonCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.api.v2.* +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection.HTTP_INTERNAL_ERROR +import java.net.HttpURLConnection.HTTP_UNAUTHORIZED + +open class IdziennikWeb(open val data: DataIdziennik) { + companion object { + const val TAG = "IdziennikWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun webApiGet(tag: String, endpoint: String, parameters: Map = emptyMap(), onSuccess: (json: JsonObject) -> Unit) { + d(tag, "Request: Idziennik/Web/API - $IDZIENNIK_WEB_URL/$endpoint") + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null && response?.parserErrorBody == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + when { + response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED + response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR + response?.parserErrorBody != null -> when { + response.parserErrorBody.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR + response.parserErrorBody.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED + response.parserErrorBody.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE + else -> ERROR_IDZIENNIK_WEB_OTHER + } + else -> null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json?.toString() ?: response?.parserErrorBody) + .withResponse(response)) + return + } + + if (json == null) { + data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + try { + onSuccess(json) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_API_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$endpoint") + .userAgent(IDZIENNIK_USER_AGENT) + .postJson() + .apply { + parameters.map { (name, value) -> + addParameter(name, value) + } + } + .allowErrorCode(HTTP_UNAUTHORIZED) + .allowErrorCode(HTTP_INTERNAL_ERROR) + .callback(callback) + .build() + .enqueue() + } + + fun webGet(tag: String, endpoint: String, onSuccess: (text: String) -> Unit) { + d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + if (!text.contains("czyWyswietlicDostepMobilny")) { + when { + text.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR + text.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED + text.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE + else -> ERROR_IDZIENNIK_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$endpoint") + .userAgent(IDZIENNIK_USER_AGENT) + .get() + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/firstlogin/IdziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/firstlogin/IdziennikFirstLogin.kt new file mode 100644 index 00000000..b282643b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/firstlogin/IdziennikFirstLogin.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-27. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.api.v2.ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR +import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_SETTINGS +import pl.szczodrzynski.edziennik.api.v2.Regexes +import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginWeb +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.swapFirstLastName + +class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikFirstLogin" + } + + private val web = IdziennikWeb(data) + private val profileList = mutableListOf() + + init { + IdziennikLoginWeb(data) { + web.webGet(TAG, IDZIENNIK_WEB_SETTINGS) { text -> + //val accounts = json.getJsonArray("accounts") + + val isParent = Regexes.IDZIENNIK_LOGIN_FIRST_IS_PARENT.find(text)?.get(1) != "0" + val accountNameLong = if (isParent) + Regexes.IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME.find(text)?.get(1)?.swapFirstLastName()?.fixName() + else + null + + var schoolYearName: String? = null + val schoolYear = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let { + schoolYearName = it[2] + it[1].toIntOrNull() + } ?: run { + data.error(ApiError(TAG, ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR) + .withApiResponse(text)) + return@webGet + } + + Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text) + .toMutableList() + .reversed() + .forEach { match -> + val registerId = match[1].toIntOrNull() ?: return@forEach + val studentId = match[2] + val firstName = match[3] + val lastName = match[4] + val className = match[5] + " " + match[6] + + val profile = Profile() + profile.studentNameLong = "$firstName $lastName".fixName() + profile.studentNameShort = "$firstName ${lastName[0]}.".fixName() + profile.accountNameLong = accountNameLong + profile.studentClassName = className + profile.studentSchoolYear = schoolYearName + profile.name = profile.studentNameLong + profile.subname = data.webUsername + profile.empty = true + profile.putStudentData("studentId", studentId) + profile.putStudentData("registerId", registerId) + profile.putStudentData("schoolYearId", schoolYear) + profileList.add(profile) + } + + EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLogin.kt new file mode 100644 index 00000000..be79e3e3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLogin.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_API +import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_WEB +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.utils.Utils + +class IdziennikLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_IDZIENNIK_WEB -> { + data.startProgress(R.string.edziennik_progress_login_idziennik_web) + IdziennikLoginWeb(data) { onSuccess(loginMethodId) } + } + LOGIN_METHOD_IDZIENNIK_API -> { + data.startProgress(R.string.edziennik_progress_login_idziennik_api) + IdziennikLoginApi(data) { onSuccess(loginMethodId) } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginApi.kt new file mode 100644 index 00000000..4782b980 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginApi.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-27. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.login + +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik + +class IdziennikLoginApi(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikLoginApi" + } + + init { run { + if (data.isApiLoginValid()) { + onSuccess() + } + else { + onSuccess() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt new file mode 100644 index 00000000..d03c254e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-26. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.login + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import okhttp3.Cookie +import pl.szczodrzynski.edziennik.api.v2.* +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.utils.Utils + +class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikLoginWeb" + } + + init { run { + if (data.isWebLoginValid()) { + data.app.cookieJar.saveFromResponse(null, listOf( + Cookie.Builder() + .name("ASP.NET_SessionId_iDziennik") + .value(data.webSessionId!!) + .domain("iuczniowie.progman.pl") + .secure().httpOnly().build(), + Cookie.Builder() + .name(".ASPXAUTH") + .value(data.webAuth!!) + .domain("iuczniowie.progman.pl") + .secure().httpOnly().build() + )) + onSuccess() + } + else { + data.app.cookieJar.clearForDomain("iuczniowie.progman.pl") + if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + private fun loginWithCredentials() { + Utils.d(TAG, "Request: Idziennik/Login/Web - $IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + + val loginCallback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + // login succeeded: there is a start page + if (text.contains("czyWyswietlicDostepMobilny")) { + val cookies = data.app.cookieJar.getForDomain("iuczniowie.progman.pl") + run { + data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.name() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION + data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.name() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH + data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.name() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER + data.loginExpiryTime = response.getUnixDate() + 45 * 60 /* 45 min */ + return@run null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + + onSuccess() + return + } + + val errorText = Regexes.IDZIENNIK_LOGIN_ERROR.find(text)?.get(1) + when { + errorText?.contains("nieprawidłową nazwę szkoły") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME + errorText?.contains("nieprawidłowy login lub hasło") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN + text.contains("Identyfikator zgłoszenia") -> ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR + text.contains("Hasło dostępu do systemu wygasło") -> ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED + text.contains("Trwają prace konserwacyjne") -> ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE + else -> ERROR_LOGIN_IDZIENNIK_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + val getCallback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + Request.builder() + .url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + .userAgent(IDZIENNIK_USER_AGENT) + .addHeader("Origin", "https://iuczniowie.progman.pl") + .addHeader("Referer", "$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + .apply { + Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text ?: return@apply).forEach { + addParameter(it[1], it[2]) + } + } + .addParameter("ctl00\$ContentPlaceHolder\$nazwaPrzegladarki", IDZIENNIK_USER_AGENT) + .addParameter("ctl00\$ContentPlaceHolder\$NazwaSzkoly", data.webSchoolName) + .addParameter("ctl00\$ContentPlaceHolder\$UserName", data.webUsername) + .addParameter("ctl00\$ContentPlaceHolder\$Password", data.webPassword) + .addParameter("ctl00\$ContentPlaceHolder\$captcha", "") + .addParameter("ctl00\$ContentPlaceHolder\$Logowanie", "Zaloguj") + .post() + .allowErrorCode(502) + .callback(loginCallback) + .build() + .enqueue() + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + .userAgent(IDZIENNIK_USER_AGENT) + .get() + .allowErrorCode(502) + .callback(getCallback) + .build() + .enqueue() + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26202e3b..f73a26fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -963,6 +963,8 @@ Pobieranie kategorii wydarzeń... Pobieranie kategorii uwag... Pobieranie zebrań z rodzicami... + Logowanie do iDziennika... + Logowanie do iDziennika... Pobieranie wiadomości wysłanych... (rodzic) (uczeń)