forked from github/szkolny
[API] Implement draft Vulcan Web login.
This commit is contained in:
parent
97dc8d12f1
commit
27e49b10fd
1
.gitignore
vendored
1
.gitignore
vendored
@ -87,3 +87,4 @@ app/schemas/
|
|||||||
signatures/
|
signatures/
|
||||||
|
|
||||||
app/.cxx
|
app/.cxx
|
||||||
|
/i18n/
|
||||||
|
@ -201,6 +201,11 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.qifan.powerpermission:powerpermission:1.0.0'
|
implementation 'com.qifan.powerpermission:powerpermission:1.0.0'
|
||||||
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.0.0'
|
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.0.0'
|
||||||
|
|
||||||
|
implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT'
|
||||||
|
implementation 'pl.droidsonroids:jspoon:1.3.2'
|
||||||
|
implementation "com.squareup.retrofit2:converter-scalars:2.8.1"
|
||||||
|
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -113,5 +113,7 @@ const val VULCAN_API_ENDPOINT_MESSAGES_ADD = "mobile-api/Uczen.v3.Uczen/DodajWia
|
|||||||
const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"
|
const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"
|
||||||
const val VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/WiadomosciZalacznik"
|
const val VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/WiadomosciZalacznik"
|
||||||
const val VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomoweZalacznik"
|
const val VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomoweZalacznik"
|
||||||
|
const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers"
|
||||||
|
const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get"
|
||||||
|
|
||||||
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"
|
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"
|
||||||
|
@ -159,6 +159,15 @@ const val ERROR_VULCAN_API_MAINTENANCE = 340
|
|||||||
const val ERROR_VULCAN_API_BAD_REQUEST = 341
|
const val ERROR_VULCAN_API_BAD_REQUEST = 341
|
||||||
const val ERROR_VULCAN_API_OTHER = 342
|
const val ERROR_VULCAN_API_OTHER = 342
|
||||||
const val ERROR_VULCAN_ATTACHMENT_DOWNLOAD = 343
|
const val ERROR_VULCAN_ATTACHMENT_DOWNLOAD = 343
|
||||||
|
const val ERROR_VULCAN_WEB_DATA_MISSING = 344
|
||||||
|
const val ERROR_VULCAN_WEB_429 = 345
|
||||||
|
const val ERROR_VULCAN_WEB_OTHER = 346
|
||||||
|
const val ERROR_VULCAN_WEB_NO_CERTIFICATE = 347
|
||||||
|
const val ERROR_VULCAN_WEB_NO_REGISTER = 348
|
||||||
|
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_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
|
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
|
||||||
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
|
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
|
||||||
@ -209,5 +218,7 @@ const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
|
|||||||
const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920
|
const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920
|
||||||
const val EXCEPTION_EDUDZIENNIK_FILE_REQUEST = 921
|
const val EXCEPTION_EDUDZIENNIK_FILE_REQUEST = 921
|
||||||
const val ERROR_ONEDRIVE_DOWNLOAD = 930
|
const val ERROR_ONEDRIVE_DOWNLOAD = 930
|
||||||
|
const val EXCEPTION_VULCAN_WEB_LOGIN = 931
|
||||||
|
const val EXCEPTION_VULCAN_WEB_REQUEST = 932
|
||||||
|
|
||||||
const val LOGIN_NO_ARGUMENTS = 1201
|
const val LOGIN_NO_ARGUMENTS = 1201
|
||||||
|
@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.Mobidzie
|
|||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi
|
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb
|
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
|
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.models.LoginMethod
|
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
|
||||||
|
|
||||||
// librus
|
// librus
|
||||||
@ -103,11 +104,11 @@ const val LOGIN_METHOD_VULCAN_WEB_OLD = 300
|
|||||||
const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400
|
const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400
|
||||||
const val LOGIN_METHOD_VULCAN_API = 500
|
const val LOGIN_METHOD_VULCAN_API = 500
|
||||||
val vulcanLoginMethods = listOf(
|
val vulcanLoginMethods = listOf(
|
||||||
/*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
|
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
|
||||||
.withIsPossible { _, _ -> false }
|
.withIsPossible { _, loginStore -> loginStore.hasLoginData("webHost") }
|
||||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
|
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
|
||||||
|
|
||||||
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java)
|
/*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java)
|
||||||
.withIsPossible { _, _ -> false }
|
.withIsPossible { _, _ -> false }
|
||||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },
|
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ val vulcanLoginMethods = listOf(
|
|||||||
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
|
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
|
||||||
.withIsPossible { _, _ -> true }
|
.withIsPossible { _, _ -> true }
|
||||||
.withRequiredLoginMethod { _, loginStore ->
|
.withRequiredLoginMethod { _, loginStore ->
|
||||||
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_NEW else LOGIN_METHOD_NOT_NEEDED
|
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -139,6 +139,9 @@ object Regexes {
|
|||||||
val VULCAN_SHIFT_ANNOTATION by lazy {
|
val VULCAN_SHIFT_ANNOTATION by lazy {
|
||||||
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
|
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
|
||||||
}
|
}
|
||||||
|
val VULCAN_WEB_PERMISSIONS by lazy {
|
||||||
|
"""permissions: '([A-z0-9\/=+\-_]+?)'""".toRegex()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class EdudziennikFirstLogin(val data: DataEdudziennik, val onSuccess: () -> Unit
|
|||||||
profileList.add(profile)
|
profileList.add(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
|
|||||||
profileList.add(profile)
|
profileList.add(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
val accounts = json.getJsonArray("accounts")
|
val accounts = json.getJsonArray("accounts")
|
||||||
|
|
||||||
if (accounts == null || accounts.size() < 1) {
|
if (accounts == null || accounts.size() < 1) {
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
return@portalGet
|
return@portalGet
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
profileList.add(profile)
|
profileList.add(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
profileList.add(profile)
|
profileList.add(profile)
|
||||||
|
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un
|
|||||||
profileList.add(profile)
|
profileList.add(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,13 @@ import pl.szczodrzynski.edziennik.values
|
|||||||
|
|
||||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||||
|
|
||||||
|
fun isWebMainLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
||||||
|
&& apiFingerprint[symbol].isNotNullNorEmpty()
|
||||||
|
&& apiPrivateKey[symbol].isNotNullNorEmpty()
|
||||||
|
&& symbol.isNotNullNorEmpty()
|
||||||
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
||||||
&& apiCertificateKey.isNotNullNorEmpty()
|
&& apiFingerprint[symbol].isNotNullNorEmpty()
|
||||||
&& apiCertificatePrivate.isNotNullNorEmpty()
|
&& apiPrivateKey[symbol].isNotNullNorEmpty()
|
||||||
&& symbol.isNotNullNorEmpty()
|
&& symbol.isNotNullNorEmpty()
|
||||||
|
|
||||||
override fun satisfyLoginMethods() {
|
override fun satisfyLoginMethods() {
|
||||||
@ -40,7 +44,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
Team.TYPE_CLASS,
|
Team.TYPE_CLASS,
|
||||||
"$schoolName:$name",
|
"$schoolCode:$name",
|
||||||
-1
|
-1
|
||||||
)
|
)
|
||||||
teamList.put(id, teamObject)
|
teamList.put(id, teamObject)
|
||||||
@ -48,7 +52,7 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generateUserCode() = "$schoolName:$studentId"
|
override fun generateUserCode() = "$schoolCode:$studentId"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A UONET+ client symbol.
|
* A UONET+ client symbol.
|
||||||
@ -59,8 +63,8 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
*/
|
*/
|
||||||
private var mSymbol: String? = null
|
private var mSymbol: String? = null
|
||||||
var symbol: String?
|
var symbol: String?
|
||||||
get() { mSymbol = mSymbol ?: loginStore.getLoginData("deviceSymbol", null); return mSymbol }
|
get() { mSymbol = mSymbol ?: profile?.getStudentData("symbol", null); return mSymbol }
|
||||||
set(value) { loginStore.putLoginData("deviceSymbol", value); mSymbol = value }
|
set(value) { profile?.putStudentData("symbol", value); mSymbol = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group symbol/number of the student's school.
|
* Group symbol/number of the student's school.
|
||||||
@ -75,16 +79,26 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value }
|
set(value) { profile?.putStudentData("schoolSymbol", value) ?: return; mSchoolSymbol = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A school ID consisting of the [symbol] and [schoolSymbol].
|
* Short name of the school, used in some places.
|
||||||
|
*
|
||||||
|
* ListaUczniow/JednostkaSprawozdawczaSkrot, e.g. "SP Wilkow"
|
||||||
|
*/
|
||||||
|
private var mSchoolShort: String? = null
|
||||||
|
var schoolShort: String?
|
||||||
|
get() { mSchoolShort = mSchoolShort ?: profile?.getStudentData("schoolShort", null); return mSchoolShort }
|
||||||
|
set(value) { profile?.putStudentData("schoolShort", value) ?: return; mSchoolShort = value }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A school code consisting of the [symbol] and [schoolSymbol].
|
||||||
*
|
*
|
||||||
* [symbol]_[schoolSymbol]
|
* [symbol]_[schoolSymbol]
|
||||||
*
|
*
|
||||||
* e.g. "poznan_000088"
|
* e.g. "poznan_000088"
|
||||||
*/
|
*/
|
||||||
private var mSchoolName: String? = null
|
private var mSchoolCode: String? = null
|
||||||
var schoolName: String?
|
var schoolCode: String?
|
||||||
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
|
get() { mSchoolCode = mSchoolCode ?: profile?.getStudentData("schoolName", null); return mSchoolCode }
|
||||||
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value }
|
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolCode = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the student.
|
* ID of the student.
|
||||||
@ -154,45 +168,34 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
* After first login only 3 first characters are stored here.
|
* After first login only 3 first characters are stored here.
|
||||||
* This is later used to determine the API URL address.
|
* This is later used to determine the API URL address.
|
||||||
*/
|
*/
|
||||||
private var mApiToken: String? = null
|
private var mApiToken: Map<String?, String?>? = null
|
||||||
var apiToken: String?
|
var apiToken: Map<String?, String?> = mapOf()
|
||||||
get() { mApiToken = mApiToken ?: loginStore.getLoginData("deviceToken", null); return mApiToken }
|
get() { mApiToken = mApiToken ?: loginStore.getLoginData("apiToken", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiToken ?: mapOf() }
|
||||||
set(value) { loginStore.putLoginData("deviceToken", value); mApiToken = value }
|
set(value) { loginStore.putLoginData("apiToken", app.gson.toJson(value)); mApiToken = value }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mobile API registration PIN.
|
* A mobile API registration PIN.
|
||||||
*
|
*
|
||||||
* After first login, this is removed and/or set to null.
|
* After first login, this is removed and/or set to null.
|
||||||
*/
|
*/
|
||||||
private var mApiPin: String? = null
|
private var mApiPin: Map<String?, String?>? = null
|
||||||
var apiPin: String?
|
var apiPin: Map<String?, String?> = mapOf()
|
||||||
get() { mApiPin = mApiPin ?: loginStore.getLoginData("devicePin", null); return mApiPin }
|
get() { mApiPin = mApiPin ?: loginStore.getLoginData("apiPin", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPin ?: mapOf() }
|
||||||
set(value) { loginStore.putLoginData("devicePin", value); mApiPin = value }
|
set(value) { loginStore.putLoginData("apiPin", app.gson.toJson(value)); mApiPin = value }
|
||||||
|
|
||||||
private var mApiCertificateKey: String? = null
|
private var mApiFingerprint: Map<String?, String?>? = null
|
||||||
var apiCertificateKey: String?
|
var apiFingerprint: Map<String?, String?> = mapOf()
|
||||||
get() { mApiCertificateKey = mApiCertificateKey ?: loginStore.getLoginData("certificateKey", null); return mApiCertificateKey }
|
get() { mApiFingerprint = mApiFingerprint ?: loginStore.getLoginData("apiFingerprint", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiFingerprint ?: mapOf() }
|
||||||
set(value) { loginStore.putLoginData("certificateKey", value); mApiCertificateKey = value }
|
set(value) { loginStore.putLoginData("apiFingerprint", app.gson.toJson(value)); mApiFingerprint = value }
|
||||||
|
|
||||||
/**
|
private var mApiPrivateKey: Map<String?, String?>? = null
|
||||||
* This is not meant for normal usage.
|
var apiPrivateKey: Map<String?, String?> = mapOf()
|
||||||
*
|
get() { mApiPrivateKey = mApiPrivateKey ?: loginStore.getLoginData("apiPrivateKey", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPrivateKey ?: mapOf() }
|
||||||
* It provides a backward compatibility (<4.0) in order
|
set(value) { loginStore.putLoginData("apiPrivateKey", app.gson.toJson(value)); mApiPrivateKey = value }
|
||||||
* to migrate and use private keys instead of PFX.
|
|
||||||
*/
|
|
||||||
private var mApiCertificatePfx: String? = null
|
|
||||||
var apiCertificatePfx: String?
|
|
||||||
get() { mApiCertificatePfx = mApiCertificatePfx ?: loginStore.getLoginData("certificatePfx", null); return mApiCertificatePfx }
|
|
||||||
set(value) { loginStore.putLoginData("certificatePfx", value); mApiCertificatePfx = value }
|
|
||||||
|
|
||||||
private var mApiCertificatePrivate: String? = null
|
|
||||||
var apiCertificatePrivate: String?
|
|
||||||
get() { mApiCertificatePrivate = mApiCertificatePrivate ?: loginStore.getLoginData("certificatePrivate", null); return mApiCertificatePrivate }
|
|
||||||
set(value) { loginStore.putLoginData("certificatePrivate", value); mApiCertificatePrivate = value }
|
|
||||||
|
|
||||||
val apiUrl: String?
|
val apiUrl: String?
|
||||||
get() {
|
get() {
|
||||||
val url = when (apiToken?.substring(0, 3)) {
|
val url = when (apiToken[symbol]?.substring(0, 3)) {
|
||||||
"3S1" -> "https://lekcjaplus.vulcan.net.pl"
|
"3S1" -> "https://lekcjaplus.vulcan.net.pl"
|
||||||
"TA1" -> "https://uonetplus-komunikacja.umt.tarnow.pl"
|
"TA1" -> "https://uonetplus-komunikacja.umt.tarnow.pl"
|
||||||
"OP1" -> "https://uonetplus-komunikacja.eszkola.opolskie.pl"
|
"OP1" -> "https://uonetplus-komunikacja.eszkola.opolskie.pl"
|
||||||
@ -217,4 +220,95 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
|||||||
get() {
|
get() {
|
||||||
return "$apiUrl$schoolSymbol/"
|
return "$apiUrl$schoolSymbol/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* __ __ _ ______ _____ _ _
|
||||||
|
\ \ / / | | | ____/ ____| | | (_)
|
||||||
|
\ \ /\ / /__| |__ | |__ | (___ | | ___ __ _ _ _ __
|
||||||
|
\ \/ \/ / _ \ '_ \ | __| \___ \ | | / _ \ / _` | | '_ \
|
||||||
|
\ /\ / __/ |_) | | | ____) | | |___| (_) | (_| | | | | |
|
||||||
|
\/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_|
|
||||||
|
__/ |
|
||||||
|
|__*/
|
||||||
|
/**
|
||||||
|
* Federation Services login type.
|
||||||
|
* This might be one of: cufs, adfs, adfslight.
|
||||||
|
*/
|
||||||
|
var webType: String?
|
||||||
|
get() { mWebType = mWebType ?: loginStore.getLoginData("webType", null); return mWebType }
|
||||||
|
set(value) { loginStore.putLoginData("webType", value); mWebType = value }
|
||||||
|
private var mWebType: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web server providing the federation services login.
|
||||||
|
* If this is present, WEB_MAIN login is considered as available.
|
||||||
|
*/
|
||||||
|
var webHost: String?
|
||||||
|
get() { mWebHost = mWebHost ?: loginStore.getLoginData("webHost", null); return mWebHost }
|
||||||
|
set(value) { loginStore.putLoginData("webHost", value); mWebHost = value }
|
||||||
|
private var mWebHost: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ID used in ADFS & ADFSLight login types.
|
||||||
|
*/
|
||||||
|
var webAdfsId: String?
|
||||||
|
get() { mWebAdfsId = mWebAdfsId ?: loginStore.getLoginData("webAdfsId", null); return mWebAdfsId }
|
||||||
|
set(value) { loginStore.putLoginData("webAdfsId", value); mWebAdfsId = value }
|
||||||
|
private var mWebAdfsId: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A domain override for ADFS Light.
|
||||||
|
*/
|
||||||
|
var webAdfsDomain: String?
|
||||||
|
get() { mWebAdfsDomain = mWebAdfsDomain ?: loginStore.getLoginData("webAdfsDomain", null); return mWebAdfsDomain }
|
||||||
|
set(value) { loginStore.putLoginData("webAdfsDomain", value); mWebAdfsDomain = value }
|
||||||
|
private var mWebAdfsDomain: String? = null
|
||||||
|
|
||||||
|
var webIsHttpCufs: Boolean
|
||||||
|
get() { mWebIsHttpCufs = mWebIsHttpCufs ?: loginStore.getLoginData("webIsHttpCufs", false); return mWebIsHttpCufs ?: false }
|
||||||
|
set(value) { loginStore.putLoginData("webIsHttpCufs", value); mWebIsHttpCufs = value }
|
||||||
|
private var mWebIsHttpCufs: Boolean? = null
|
||||||
|
|
||||||
|
var webIsScopedAdfs: Boolean
|
||||||
|
get() { mWebIsScopedAdfs = mWebIsScopedAdfs ?: loginStore.getLoginData("webIsScopedAdfs", false); return mWebIsScopedAdfs ?: false }
|
||||||
|
set(value) { loginStore.putLoginData("webIsScopedAdfs", value); mWebIsScopedAdfs = value }
|
||||||
|
private var mWebIsScopedAdfs: Boolean? = null
|
||||||
|
|
||||||
|
var webEmail: String?
|
||||||
|
get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail }
|
||||||
|
set(value) { loginStore.putLoginData("webEmail", value); mWebEmail = value }
|
||||||
|
private var mWebEmail: String? = null
|
||||||
|
var webUsername: String?
|
||||||
|
get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("webUsername", null); return mWebUsername }
|
||||||
|
set(value) { loginStore.putLoginData("webUsername", value); mWebUsername = value }
|
||||||
|
private var mWebUsername: String? = null
|
||||||
|
var webPassword: String?
|
||||||
|
get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("webPassword", null); return mWebPassword }
|
||||||
|
set(value) { loginStore.putLoginData("webPassword", value); mWebPassword = value }
|
||||||
|
private var mWebPassword: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expiry time of a certificate POSTed to a LoginEndpoint of the specific symbol.
|
||||||
|
* If the time passes, the certificate needs to be POSTed again (if valid)
|
||||||
|
* or re-generated.
|
||||||
|
*/
|
||||||
|
var webExpiryTime: Long
|
||||||
|
get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L }
|
||||||
|
set(value) { profile?.putStudentData("webExpiryTime", value); mWebExpiryTime = value }
|
||||||
|
private var mWebExpiryTime: Long? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EfebSsoAuthCookie retrieved after posting a certificate
|
||||||
|
*/
|
||||||
|
var webAuthCookie: String?
|
||||||
|
get() { mWebAuthCookie = mWebAuthCookie ?: profile?.getStudentData("webAuthCookie", null); return mWebAuthCookie }
|
||||||
|
set(value) { profile?.putStudentData("webAuthCookie", value); mWebAuthCookie = value }
|
||||||
|
private var mWebAuthCookie: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permissions needed to get JSONs from home page
|
||||||
|
*/
|
||||||
|
var webPermissions: String?
|
||||||
|
get() { mWebPermissions = mWebPermissions ?: profile?.getStudentData("webPermissions", null); return mWebPermissions }
|
||||||
|
set(value) { profile?.putStudentData("webPermissions", value); mWebPermissions = value }
|
||||||
|
private var mWebPermissions: String? = null
|
||||||
}
|
}
|
||||||
|
@ -106,11 +106,11 @@ open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
|
|||||||
Request.builder()
|
Request.builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.userAgent(VULCAN_API_USER_AGENT)
|
.userAgent(VULCAN_API_USER_AGENT)
|
||||||
.addHeader("RequestCertificateKey", data.apiCertificateKey)
|
.addHeader("RequestCertificateKey", data.apiFingerprint[data.symbol])
|
||||||
.addHeader("RequestSignatureValue",
|
.addHeader("RequestSignatureValue",
|
||||||
try {
|
try {
|
||||||
signContent(
|
signContent(
|
||||||
data.apiCertificatePrivate ?: "",
|
data.apiPrivateKey[data.symbol] ?: "",
|
||||||
finalPayload.toString()
|
finalPayload.toString()
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {e.printStackTrace();""})
|
} catch (e: Exception) {e.printStackTrace();""})
|
||||||
|
@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-4-17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import im.wangchao.mhttp.Request
|
||||||
|
import im.wangchao.mhttp.Response
|
||||||
|
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||||
|
import pl.droidsonroids.jspoon.Jspoon
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
|
import pl.szczodrzynski.edziennik.get
|
||||||
|
import pl.szczodrzynski.edziennik.isNotNullNorBlank
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
import java.io.File
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
|
||||||
|
open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "VulcanWebMain"
|
||||||
|
const val WEB_MAIN = 0
|
||||||
|
const val WEB_OLD = 1
|
||||||
|
const val WEB_NEW = 2
|
||||||
|
const val WEB_MESSAGES = 3
|
||||||
|
const val STATE_SUCCESS = 0
|
||||||
|
const val STATE_NO_REGISTER = 1
|
||||||
|
const val STATE_LOGGED_OUT = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
val profileId
|
||||||
|
get() = data.profile?.id ?: -1
|
||||||
|
|
||||||
|
val profile
|
||||||
|
get() = data.profile
|
||||||
|
|
||||||
|
private val certificateAdapter by lazy {
|
||||||
|
Jspoon.create().adapter(CufsCertificate::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveCertificate(certificate: String) {
|
||||||
|
val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml")
|
||||||
|
file.writeText(certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readCertificate(): String? {
|
||||||
|
val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml")
|
||||||
|
if (file.canRead())
|
||||||
|
return file.readText()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseCertificate(certificate: String): CufsCertificate {
|
||||||
|
val xml = certificate
|
||||||
|
.replace("<[a-z]+?:".toRegex(), "<")
|
||||||
|
.replace("</[a-z]+?:".toRegex(), "</")
|
||||||
|
.replace("\\sxmlns.*?=\".+?\"".toRegex(), "")
|
||||||
|
|
||||||
|
return certificateAdapter.fromHtml(xml)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postCertificate(certificate: String, symbol: String, onResult: (symbol: String, state: Int) -> Unit): Boolean {
|
||||||
|
val cufsCertificate = parseCertificate(certificate)
|
||||||
|
|
||||||
|
// check if the certificate is valid
|
||||||
|
if (Date.fromIso(cufsCertificate.expiryDate) < System.currentTimeMillis())
|
||||||
|
return false
|
||||||
|
|
||||||
|
val callback = object : TextCallbackHandler() {
|
||||||
|
override fun onSuccess(text: String?, response: Response?) {
|
||||||
|
if (response?.headers()?.get("Location")?.contains("LoginEndpoint.aspx") == true
|
||||||
|
|| response?.headers()?.get("Location")?.contains("?logout=true") == true) {
|
||||||
|
onResult(symbol, STATE_LOGGED_OUT)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (text?.contains("LoginEndpoint.aspx?logout=true") == true) {
|
||||||
|
onResult(symbol, STATE_NO_REGISTER)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!validateCallback(text, response, jsonResponse = false)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onResult(symbol, STATE_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||||
|
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
|
||||||
|
.withResponse(response)
|
||||||
|
.withThrowable(throwable))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request.builder()
|
||||||
|
.url("https://uonetplus.${data.webHost}/$symbol/LoginEndpoint.aspx")
|
||||||
|
.withClient(data.app.httpLazy)
|
||||||
|
.userAgent(SYSTEM_USER_AGENT)
|
||||||
|
.post()
|
||||||
|
.addParameter("wa", "wsignin1.0")
|
||||||
|
.addParameter("wctx", cufsCertificate.targetUrl)
|
||||||
|
.addParameter("wresult", certificate)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE)
|
||||||
|
.allowErrorCode(429)
|
||||||
|
.callback(callback)
|
||||||
|
.build()
|
||||||
|
.enqueue()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List<String>) -> Unit) {
|
||||||
|
val callback = object : TextCallbackHandler() {
|
||||||
|
override fun onSuccess(text: String?, response: Response?) {
|
||||||
|
if (!validateCallback(text, response, jsonResponse = false) || text == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postErrors) {
|
||||||
|
when {
|
||||||
|
text.contains("status absolwenta") -> ERROR_VULCAN_WEB_GRADUATE_ACCOUNT
|
||||||
|
else -> null
|
||||||
|
}?.let { errorCode ->
|
||||||
|
data.error(ApiError(TAG, errorCode)
|
||||||
|
.withResponse(response)
|
||||||
|
.withApiResponse(text))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
|
||||||
|
|
||||||
|
val schoolSymbols = mutableListOf<String>()
|
||||||
|
val clientUrl = "https://uonetplus-uczen.${data.webHost}/$symbol/"
|
||||||
|
var clientIndex = text.indexOf(clientUrl)
|
||||||
|
var count = 0
|
||||||
|
while (clientIndex != -1 && count < 100) {
|
||||||
|
val startIndex = clientIndex + clientUrl.length
|
||||||
|
val endIndex = text.indexOf('/', startIndex = startIndex)
|
||||||
|
val schoolSymbol = text.substring(startIndex, endIndex)
|
||||||
|
schoolSymbols += schoolSymbol
|
||||||
|
clientIndex = text.indexOf(clientUrl, startIndex = endIndex)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(text, schoolSymbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||||
|
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
|
||||||
|
.withResponse(response)
|
||||||
|
.withThrowable(throwable))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request.builder()
|
||||||
|
.url("https://uonetplus.${data.webHost}/$symbol/Start.mvc/Index")
|
||||||
|
.userAgent(SYSTEM_USER_AGENT)
|
||||||
|
.get()
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
|
||||||
|
.allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE)
|
||||||
|
.allowErrorCode(429)
|
||||||
|
.callback(callback)
|
||||||
|
.build()
|
||||||
|
.enqueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateCallback(text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
|
||||||
|
if (text == null) {
|
||||||
|
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||||
|
.withResponse(response))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response?.code() !in 200..302 || (jsonResponse && !text.startsWith("{"))) {
|
||||||
|
when {
|
||||||
|
text.contains("The custom error module") -> ERROR_VULCAN_WEB_429
|
||||||
|
else -> ERROR_VULCAN_WEB_OTHER
|
||||||
|
}.let { errorCode ->
|
||||||
|
data.error(ApiError(TAG, errorCode)
|
||||||
|
.withApiResponse(text)
|
||||||
|
.withResponse(response))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl")
|
||||||
|
val authCookie = cookies["EfebSsoAuthCookie"]
|
||||||
|
if ((authCookie == null || authCookie == "null") && data.webAuthCookie != null) {
|
||||||
|
data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie)
|
||||||
|
}
|
||||||
|
else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie) {
|
||||||
|
data.webAuthCookie = authCookie
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun webGetJson(
|
||||||
|
tag: String,
|
||||||
|
webType: Int,
|
||||||
|
endpoint: String,
|
||||||
|
method: Int = POST,
|
||||||
|
parameters: Map<String, Any> = emptyMap(),
|
||||||
|
onSuccess: (json: JsonObject, response: Response?) -> Unit
|
||||||
|
) {
|
||||||
|
val url = "https://" + when (webType) {
|
||||||
|
WEB_MAIN -> "uonetplus"
|
||||||
|
WEB_OLD -> "uonetplus-opiekun"
|
||||||
|
WEB_NEW -> "uonetplus-uczen"
|
||||||
|
WEB_MESSAGES -> "uonetplus-uzytkownik"
|
||||||
|
else -> "uonetplus"
|
||||||
|
} + ".${data.webHost}/${data.symbol}/$endpoint"
|
||||||
|
|
||||||
|
Utils.d(tag, "Request: Vulcan/WebMain - $url")
|
||||||
|
|
||||||
|
val payload = JsonObject()
|
||||||
|
parameters.map { (name, value) ->
|
||||||
|
when (value) {
|
||||||
|
is JsonObject -> payload.add(name, value)
|
||||||
|
is JsonArray -> payload.add(name, value)
|
||||||
|
is String -> payload.addProperty(name, value)
|
||||||
|
is Int -> payload.addProperty(name, value)
|
||||||
|
is Long -> payload.addProperty(name, value)
|
||||||
|
is Float -> payload.addProperty(name, value)
|
||||||
|
is Char -> payload.addProperty(name, value)
|
||||||
|
is Boolean -> payload.addProperty(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : TextCallbackHandler() {
|
||||||
|
override fun onSuccess(text: String?, response: Response?) {
|
||||||
|
if (!validateCallback(text, response))
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val json = JsonParser().parse(text).asJsonObject
|
||||||
|
onSuccess(json, response)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.error(ApiError(tag, EXCEPTION_VULCAN_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(url)
|
||||||
|
.userAgent(SYSTEM_USER_AGENT)
|
||||||
|
.apply {
|
||||||
|
when (method) {
|
||||||
|
GET -> get()
|
||||||
|
POST -> post()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setJsonBody(payload)
|
||||||
|
.allowErrorCode(429)
|
||||||
|
.callback(callback)
|
||||||
|
.build()
|
||||||
|
.enqueue()
|
||||||
|
}
|
||||||
|
}
|
@ -80,7 +80,7 @@ class VulcanApiTimetable(override val data: DataVulcan,
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
Team.TYPE_VIRTUAL,
|
Team.TYPE_VIRTUAL,
|
||||||
"${data.schoolName}:$name",
|
"${data.schoolCode}:$name",
|
||||||
teacherId ?: oldTeacherId ?: -1
|
teacherId ?: oldTeacherId ?: -1
|
||||||
)
|
)
|
||||||
data.teamList[id] = team
|
data.teamList[id] = team
|
||||||
|
@ -6,12 +6,14 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin
|
|||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_STUDENT_LIST
|
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
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.VulcanApi
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
|
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
|
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||||
|
|
||||||
@ -21,19 +23,92 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val api = VulcanApi(data, null)
|
private val api = VulcanApi(data, null)
|
||||||
|
private val web = VulcanWebMain(data, null)
|
||||||
private val profileList = mutableListOf<Profile>()
|
private val profileList = mutableListOf<Profile>()
|
||||||
|
private val loginStoreId = data.loginStore.id
|
||||||
|
private var firstProfileId = loginStoreId
|
||||||
|
private val tryingSymbols = mutableListOf<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val loginStoreId = data.loginStore.id
|
if (data.loginStore.mode == LOGIN_MODE_VULCAN_WEB) {
|
||||||
val loginStoreType = LOGIN_TYPE_VULCAN
|
VulcanLoginWebMain(data) {
|
||||||
var firstProfileId = loginStoreId
|
val certificate = web.readCertificate() ?: run {
|
||||||
|
data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_CERTIFICATE))
|
||||||
|
return@VulcanLoginWebMain
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.symbol != null && data.symbol != "default") {
|
||||||
|
tryingSymbols += data.symbol ?: "default"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val cufsCertificate = web.parseCertificate(certificate)
|
||||||
|
tryingSymbols += cufsCertificate.userInstances
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSymbol(certificate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
registerDevice {
|
||||||
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkSymbol(certificate: String) {
|
||||||
|
if (tryingSymbols.isEmpty()) {
|
||||||
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||||
|
onSuccess()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = web.postCertificate(certificate, tryingSymbols.removeAt(0)) { symbol, state ->
|
||||||
|
when (state) {
|
||||||
|
VulcanWebMain.STATE_NO_REGISTER -> {
|
||||||
|
checkSymbol(certificate)
|
||||||
|
}
|
||||||
|
VulcanWebMain.STATE_LOGGED_OUT -> data.error(ApiError(TAG, ERROR_VULCAN_WEB_LOGGED_OUT))
|
||||||
|
VulcanWebMain.STATE_SUCCESS -> {
|
||||||
|
webRegisterDevice(symbol) {
|
||||||
|
checkSymbol(certificate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// postCertificate returns false if the cert is not valid anymore
|
||||||
|
if (!result) {
|
||||||
|
data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED)
|
||||||
|
.withApiResponse(certificate))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun webRegisterDevice(symbol: String, onSuccess: () -> Unit) {
|
||||||
|
web.getStartPage(symbol, postErrors = false) { _, schoolSymbols ->
|
||||||
|
data.symbol = symbol
|
||||||
|
val schoolSymbol = data.schoolSymbol ?: schoolSymbols.firstOrNull()
|
||||||
|
web.webGetJson(TAG, VulcanWebMain.WEB_NEW, "$schoolSymbol/$VULCAN_WEB_ENDPOINT_REGISTER_DEVICE") { result, _ ->
|
||||||
|
val json = result.getJsonObject("data")
|
||||||
|
data.symbol = symbol
|
||||||
|
data.apiToken = data.apiToken.toMutableMap().also {
|
||||||
|
it[symbol] = json.getString("TokenKey")
|
||||||
|
}
|
||||||
|
data.apiPin = data.apiPin.toMutableMap().also {
|
||||||
|
it[symbol] = json.getString("PIN")
|
||||||
|
}
|
||||||
|
registerDevice(onSuccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerDevice(onSuccess: () -> Unit) {
|
||||||
VulcanLoginApi(data) {
|
VulcanLoginApi(data) {
|
||||||
api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, response ->
|
api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, _ ->
|
||||||
val students = json.getJsonArray("Data")
|
val students = json.getJsonArray("Data")
|
||||||
|
|
||||||
if (students == null || students.isEmpty()) {
|
if (students == null || students.isEmpty()) {
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore))
|
||||||
onSuccess()
|
onSuccess()
|
||||||
return@apiGet
|
return@apiGet
|
||||||
}
|
}
|
||||||
@ -42,7 +117,8 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
val student = studentEl.asJsonObject
|
val student = studentEl.asJsonObject
|
||||||
|
|
||||||
val schoolSymbol = student.getString("JednostkaSprawozdawczaSymbol") ?: return@forEach
|
val schoolSymbol = student.getString("JednostkaSprawozdawczaSymbol") ?: return@forEach
|
||||||
val schoolName = "${data.symbol}_$schoolSymbol"
|
val schoolShort = student.getString("JednostkaSprawozdawczaSkrot") ?: return@forEach
|
||||||
|
val schoolCode = "${data.symbol}_$schoolSymbol"
|
||||||
val studentId = student.getInt("Id") ?: return@forEach
|
val studentId = student.getInt("Id") ?: return@forEach
|
||||||
val studentLoginId = student.getInt("UzytkownikLoginId") ?: return@forEach
|
val studentLoginId = student.getInt("UzytkownikLoginId") ?: return@forEach
|
||||||
val studentClassId = student.getInt("IdOddzial") ?: return@forEach
|
val studentClassId = student.getInt("IdOddzial") ?: return@forEach
|
||||||
@ -80,7 +156,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
val profile = Profile(
|
val profile = Profile(
|
||||||
firstProfileId++,
|
firstProfileId++,
|
||||||
loginStoreId,
|
loginStoreId,
|
||||||
loginStoreType,
|
LOGIN_TYPE_VULCAN,
|
||||||
studentNameLong,
|
studentNameLong,
|
||||||
userLogin,
|
userLogin,
|
||||||
studentNameLong,
|
studentNameLong,
|
||||||
@ -88,13 +164,16 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
accountName
|
accountName
|
||||||
).apply {
|
).apply {
|
||||||
this.studentClassName = studentClassName
|
this.studentClassName = studentClassName
|
||||||
|
studentData["symbol"] = data.symbol
|
||||||
|
|
||||||
studentData["studentId"] = studentId
|
studentData["studentId"] = studentId
|
||||||
studentData["studentLoginId"] = studentLoginId
|
studentData["studentLoginId"] = studentLoginId
|
||||||
studentData["studentClassId"] = studentClassId
|
studentData["studentClassId"] = studentClassId
|
||||||
studentData["studentSemesterId"] = studentSemesterId
|
studentData["studentSemesterId"] = studentSemesterId
|
||||||
studentData["studentSemesterNumber"] = studentSemesterNumber
|
studentData["studentSemesterNumber"] = studentSemesterNumber
|
||||||
studentData["schoolSymbol"] = schoolSymbol
|
studentData["schoolSymbol"] = schoolSymbol
|
||||||
studentData["schoolName"] = schoolName
|
studentData["schoolShort"] = schoolShort
|
||||||
|
studentData["schoolName"] = schoolCode
|
||||||
studentData["currentSemesterEndDate"] = currentSemesterEndDate
|
studentData["currentSemesterEndDate"] = currentSemesterEndDate
|
||||||
}
|
}
|
||||||
dateSemester1Start?.let {
|
dateSemester1Start?.let {
|
||||||
@ -107,7 +186,6 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
profileList.add(profile)
|
profileList.add(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
|
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-4-17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
|
||||||
|
|
||||||
|
import pl.droidsonroids.jspoon.annotation.Selector
|
||||||
|
|
||||||
|
class CufsCertificate {
|
||||||
|
@Selector(value = "EndpointReference Address")
|
||||||
|
var targetUrl: String = ""
|
||||||
|
|
||||||
|
@Selector(value = "Lifetime Created")
|
||||||
|
var createdDate: String = ""
|
||||||
|
|
||||||
|
@Selector(value = "Lifetime Expires")
|
||||||
|
var expiryDate: String = ""
|
||||||
|
|
||||||
|
@Selector(value = "Attribute[AttributeName=UserInstance] AttributeValue")
|
||||||
|
var userInstances: List<String> = listOf()
|
||||||
|
}
|
@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
|
|||||||
|
|
||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
|
||||||
@ -45,6 +46,10 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
Utils.d(TAG, "Using login method $loginMethodId")
|
Utils.d(TAG, "Using login method $loginMethodId")
|
||||||
when (loginMethodId) {
|
when (loginMethodId) {
|
||||||
|
LOGIN_METHOD_VULCAN_WEB_MAIN -> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_login_vulcan_web_main)
|
||||||
|
VulcanLoginWebMain(data) { onSuccess(loginMethodId) }
|
||||||
|
}
|
||||||
LOGIN_METHOD_VULCAN_API -> {
|
LOGIN_METHOD_VULCAN_API -> {
|
||||||
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
|
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
|
||||||
VulcanLoginApi(data) { onSuccess(loginMethodId) }
|
VulcanLoginApi(data) { onSuccess(loginMethodId) }
|
||||||
|
@ -10,14 +10,11 @@ import im.wangchao.mhttp.Request
|
|||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
import im.wangchao.mhttp.callback.JsonCallbackHandler
|
||||||
import io.github.wulkanowy.signer.android.getPrivateKeyFromCert
|
import io.github.wulkanowy.signer.android.getPrivateKeyFromCert
|
||||||
import pl.szczodrzynski.edziennik.currentTimeUnix
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiUpdateSemester
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiUpdateSemester
|
||||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.getJsonObject
|
|
||||||
import pl.szczodrzynski.edziennik.getString
|
|
||||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||||
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
|
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -29,28 +26,14 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init { run {
|
init { run {
|
||||||
|
copyFromLoginStore()
|
||||||
|
|
||||||
if (data.profile != null && data.isApiLoginValid()) {
|
if (data.profile != null && data.isApiLoginValid()) {
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// < v4.0 - PFX to Private Key migration
|
if (data.apiFingerprint[data.symbol].isNotNullNorEmpty()
|
||||||
if (data.apiCertificatePfx.isNotNullNorEmpty()) {
|
&& data.apiPrivateKey[data.symbol].isNotNullNorEmpty()
|
||||||
try {
|
|
||||||
data.apiCertificatePrivate = getPrivateKeyFromCert(
|
|
||||||
if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
|
|
||||||
data.apiCertificatePfx ?: ""
|
|
||||||
)
|
|
||||||
data.loginStore.removeLoginData("certificatePfx")
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
onSuccess()
|
|
||||||
return@run
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.apiCertificateKey.isNotNullNorEmpty()
|
|
||||||
&& data.apiCertificatePrivate.isNotNullNorEmpty()
|
|
||||||
&& data.symbol.isNotNullNorEmpty()) {
|
&& data.symbol.isNotNullNorEmpty()) {
|
||||||
// (see data.isApiLoginValid())
|
// (see data.isApiLoginValid())
|
||||||
// the semester end date is over
|
// the semester end date is over
|
||||||
@ -58,7 +41,7 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
return@run
|
return@run
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.symbol.isNotNullNorEmpty() && data.apiToken.isNotNullNorEmpty() && data.apiPin.isNotNullNorEmpty()) {
|
if (data.symbol.isNotNullNorEmpty() && data.apiToken[data.symbol].isNotNullNorEmpty() && data.apiPin[data.symbol].isNotNullNorEmpty()) {
|
||||||
loginWithToken()
|
loginWithToken()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -67,6 +50,64 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
private fun copyFromLoginStore() {
|
||||||
|
data.loginStore.data.apply {
|
||||||
|
// < v4.0 - PFX to Private Key migration
|
||||||
|
if (has("certificatePfx")) {
|
||||||
|
try {
|
||||||
|
val privateKey = getPrivateKeyFromCert(
|
||||||
|
if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
|
||||||
|
getString("certificatePfx") ?: ""
|
||||||
|
)
|
||||||
|
data.apiPrivateKey = mapOf(
|
||||||
|
data.symbol to privateKey
|
||||||
|
)
|
||||||
|
remove("certificatePfx")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.0 - new login form - copy user input to profile
|
||||||
|
if (has("symbol")) {
|
||||||
|
data.symbol = getString("symbol")
|
||||||
|
remove("symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.0 - before Vulcan Web impl - migrate from strings to Map of Symbol to String
|
||||||
|
if (has("deviceSymbol")) {
|
||||||
|
data.symbol = getString("deviceSymbol")
|
||||||
|
remove("deviceSymbol")
|
||||||
|
}
|
||||||
|
if (has("certificateKey")) {
|
||||||
|
data.apiFingerprint = data.apiFingerprint.toMutableMap().also {
|
||||||
|
it[data.symbol] = getString("certificateKey")
|
||||||
|
}
|
||||||
|
remove("certificateKey")
|
||||||
|
}
|
||||||
|
if (has("certificatePrivate")) {
|
||||||
|
data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also {
|
||||||
|
it[data.symbol] = getString("certificatePrivate")
|
||||||
|
}
|
||||||
|
remove("certificatePrivate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// map form inputs to the symbol
|
||||||
|
if (has("deviceToken")) {
|
||||||
|
data.apiToken = data.apiToken.toMutableMap().also {
|
||||||
|
it[data.symbol] = getString("deviceToken")
|
||||||
|
}
|
||||||
|
remove("deviceToken")
|
||||||
|
}
|
||||||
|
if (has("devicePin")) {
|
||||||
|
data.apiPin = data.apiPin.toMutableMap().also {
|
||||||
|
it[data.symbol] = getString("devicePin")
|
||||||
|
}
|
||||||
|
remove("devicePin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loginWithToken() {
|
private fun loginWithToken() {
|
||||||
d(TAG, "Request: Vulcan/Login/Api - ${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE")
|
d(TAG, "Request: Vulcan/Login/Api - ${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE")
|
||||||
|
|
||||||
@ -118,14 +159,22 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data.apiCertificateKey = cert.getString("CertyfikatKlucz")
|
val privateKey = getPrivateKeyFromCert(
|
||||||
data.apiToken = data.apiToken?.substring(0, 3)
|
if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
|
||||||
data.apiCertificatePrivate = getPrivateKeyFromCert(
|
|
||||||
if (data.apiToken?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
|
|
||||||
cert.getString("CertyfikatPfx") ?: ""
|
cert.getString("CertyfikatPfx") ?: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data.apiFingerprint = data.apiFingerprint.toMutableMap().also {
|
||||||
|
it[data.symbol] = cert.getString("CertyfikatKlucz")
|
||||||
|
}
|
||||||
|
data.apiToken = data.apiToken.toMutableMap().also {
|
||||||
|
it[data.symbol] = it[data.symbol]?.substring(0, 3)
|
||||||
|
}
|
||||||
|
data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also {
|
||||||
|
it[data.symbol] = privateKey
|
||||||
|
}
|
||||||
data.loginStore.removeLoginData("certificatePfx")
|
data.loginStore.removeLoginData("certificatePfx")
|
||||||
data.loginStore.removeLoginData("devicePin")
|
data.loginStore.removeLoginData("apiPin")
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,14 +185,26 @@ class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val deviceId = data.app.deviceId.padStart(16, '0')
|
||||||
|
val loginStoreId = data.loginStore.id.toString(16).padStart(4, '0')
|
||||||
|
val symbol = data.symbol?.crc16()?.toString(16)?.take(2) ?: "00"
|
||||||
|
val uuid =
|
||||||
|
deviceId.substring(0..7) +
|
||||||
|
"-" + deviceId.substring(8..11) +
|
||||||
|
"-" + deviceId.substring(12..15) +
|
||||||
|
"-" + loginStoreId +
|
||||||
|
"-" + symbol + "6f72616e7a"
|
||||||
|
|
||||||
|
val deviceNameSuffix = " - nie usuwać"
|
||||||
|
|
||||||
Request.builder()
|
Request.builder()
|
||||||
.url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
|
.url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
|
||||||
.userAgent(VULCAN_API_USER_AGENT)
|
.userAgent(VULCAN_API_USER_AGENT)
|
||||||
.addHeader("RequestMobileType", "RegisterDevice")
|
.addHeader("RequestMobileType", "RegisterDevice")
|
||||||
.addParameter("PIN", data.apiPin)
|
.addParameter("PIN", data.apiPin[data.symbol])
|
||||||
.addParameter("TokenKey", data.apiToken)
|
.addParameter("TokenKey", data.apiToken[data.symbol])
|
||||||
.addParameter("DeviceId", UUID.randomUUID().toString())
|
.addParameter("DeviceId", uuid)
|
||||||
.addParameter("DeviceName", VULCAN_API_DEVICE_NAME)
|
.addParameter("DeviceName", VULCAN_API_DEVICE_NAME.take(50 - deviceNameSuffix.length) + deviceNameSuffix)
|
||||||
.addParameter("DeviceNameUser", "")
|
.addParameter("DeviceNameUser", "")
|
||||||
.addParameter("DeviceDescription", "")
|
.addParameter("DeviceDescription", "")
|
||||||
.addParameter("DeviceSystemType", "Android")
|
.addParameter("DeviceSystemType", "Android")
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-4-16.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||||
|
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.fslogin.FSLogin
|
||||||
|
import pl.szczodrzynski.fslogin.realm.CufsRealm
|
||||||
|
|
||||||
|
class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "VulcanLoginWebMain"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val web by lazy { VulcanWebMain(data, null) }
|
||||||
|
|
||||||
|
init { run {
|
||||||
|
copyFromLoginStore()
|
||||||
|
|
||||||
|
if (data.profile != null && data.isWebMainLoginValid()) {
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (data.symbol.isNotNullNorEmpty()
|
||||||
|
&& data.webType.isNotNullNorEmpty()
|
||||||
|
&& data.webHost.isNotNullNorEmpty()
|
||||||
|
&& (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty())
|
||||||
|
&& data.webPassword.isNotNullNorEmpty()) {
|
||||||
|
try {
|
||||||
|
val success = loginWithCredentials()
|
||||||
|
if (!success)
|
||||||
|
data.error(ApiError(TAG, ERROR_VULCAN_WEB_DATA_MISSING))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.error(ApiError(TAG, EXCEPTION_VULCAN_WEB_LOGIN)
|
||||||
|
.withThrowable(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
private fun copyFromLoginStore() {
|
||||||
|
data.loginStore.data.apply {
|
||||||
|
// 4.0 - new login form - copy user input to profile
|
||||||
|
if (has("symbol")) {
|
||||||
|
data.symbol = getString("symbol")
|
||||||
|
remove("symbol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loginWithCredentials(): Boolean {
|
||||||
|
val realm = when (data.webType) {
|
||||||
|
"cufs" -> CufsRealm(
|
||||||
|
host = data.webHost ?: return false,
|
||||||
|
symbol = data.symbol ?: "default",
|
||||||
|
httpCufs = data.webIsHttpCufs
|
||||||
|
)
|
||||||
|
"adfs" -> CufsRealm(
|
||||||
|
host = data.webHost ?: return false,
|
||||||
|
symbol = data.symbol ?: "default",
|
||||||
|
httpCufs = data.webIsHttpCufs
|
||||||
|
).toAdfsRealm(id = data.webAdfsId ?: return false)
|
||||||
|
"adfslight" -> CufsRealm(
|
||||||
|
host = data.webHost ?: return false,
|
||||||
|
symbol = data.symbol ?: "default",
|
||||||
|
httpCufs = data.webIsHttpCufs
|
||||||
|
).toAdfsLightRealm(
|
||||||
|
id = data.webAdfsId ?: return false,
|
||||||
|
domain = data.webAdfsDomain ?: "adfslight",
|
||||||
|
isScoped = data.webIsScopedAdfs
|
||||||
|
)
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { errorText ->
|
||||||
|
// TODO
|
||||||
|
data.error(ApiError(TAG, 0).withThrowable(RuntimeException(errorText)))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -152,7 +152,7 @@ object LoginInfo {
|
|||||||
caseMode = Credential.CaseMode.UPPER_CASE
|
caseMode = Credential.CaseMode.UPPER_CASE
|
||||||
),
|
),
|
||||||
Credential(
|
Credential(
|
||||||
keyName = "deviceSymbol",
|
keyName = "symbol",
|
||||||
name = R.string.login_hint_symbol,
|
name = R.string.login_hint_symbol,
|
||||||
icon = CommunityMaterial.Icon2.cmd_school,
|
icon = CommunityMaterial.Icon2.cmd_school,
|
||||||
emptyText = R.string.login_error_no_symbol,
|
emptyText = R.string.login_error_no_symbol,
|
||||||
|
@ -91,7 +91,7 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||||
fun onFirstLoginFinishedEvent(event: FirstLoginFinishedEvent) {
|
fun onFirstLoginFinishedEvent(event: FirstLoginFinishedEvent) {
|
||||||
if (event.profileList.isEmpty()) {
|
if (event.profileList.isEmpty()) {
|
||||||
MaterialAlertDialogBuilder(activity)
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
@ -85,7 +85,7 @@ class LoginSyncFragment : Fragment(), CoroutineScope {
|
|||||||
).concat(" ")
|
).concat(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||||
fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) {
|
fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) {
|
||||||
nav.navigate(R.id.loginFinishFragment, finishArguments, activity.navOptions)
|
nav.navigate(R.id.loginFinishFragment, finishArguments, activity.navOptions)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.ExtensionsKt;
|
import pl.szczodrzynski.edziennik.ExtensionsKt;
|
||||||
import pl.szczodrzynski.edziennik.R;
|
import pl.szczodrzynski.edziennik.R;
|
||||||
@ -108,7 +109,11 @@ public class Date implements Comparable<Date> {
|
|||||||
|
|
||||||
public static long fromIso(String dateTime) {
|
public static long fromIso(String dateTime) {
|
||||||
try {
|
try {
|
||||||
return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19))));
|
Calendar c = Calendar.getInstance();
|
||||||
|
c.set(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(5, 7)) - 1, Integer.parseInt(dateTime.substring(8, 10)), Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19)));
|
||||||
|
c.set(Calendar.MILLISECOND, 0);
|
||||||
|
c.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
return c.getTimeInMillis();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
return System.currentTimeMillis();
|
return System.currentTimeMillis();
|
||||||
|
@ -1230,4 +1230,7 @@
|
|||||||
<string name="yesterday">yesterday</string>
|
<string name="yesterday">yesterday</string>
|
||||||
<string name="you_are_offline_text">You\'re offline. Try enabling Wi-Fi or mobile data.</string>
|
<string name="you_are_offline_text">You\'re offline. Try enabling Wi-Fi or mobile data.</string>
|
||||||
<string name="you_are_offline_title">Internet connection</string>
|
<string name="you_are_offline_title">Internet connection</string>
|
||||||
|
<string name="permissions_attachment">In order to download the file, you have to grant file storage permission for the application.\n\nClick OK to grant the permission.</string>
|
||||||
|
<string name="permissions_denied">You denied the required permissions for the application.\n\nIn order to grant the permission, open the Permissions screen for Szkolny.eu in phone settings.\n\nClick OK to open app settings now.</string>
|
||||||
|
<string name="permissions_required">Required permissions</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1288,19 +1288,19 @@
|
|||||||
<string name="you_are_offline_title">Połączenie sieciowe</string>
|
<string name="you_are_offline_title">Połączenie sieciowe</string>
|
||||||
<string name="login_chooser_title">Jaki masz e-dziennik w szkole?</string>
|
<string name="login_chooser_title">Jaki masz e-dziennik w szkole?</string>
|
||||||
<string name="login_chooser_subtitle">Wybierz z jakiego e-dziennika korzysta Twoja szkoła. Jeśli masz kilka kont w różnych dziennikach, będziesz mógł je dodać później.</string>
|
<string name="login_chooser_subtitle">Wybierz z jakiego e-dziennika korzysta Twoja szkoła. Jeśli masz kilka kont w różnych dziennikach, będziesz mógł je dodać później.</string>
|
||||||
<string name="login_register_librus">Librus/Synergia</string>
|
<string name="login_register_librus" translatable="false">Librus/Synergia</string>
|
||||||
<string name="login_mode_librus_email">Zaloguj używając e-maila</string>
|
<string name="login_mode_librus_email">Zaloguj używając e-maila</string>
|
||||||
<string name="login_mode_librus_email_hint">Musisz posiadać konto Librus Rodzina</string>
|
<string name="login_mode_librus_email_hint">Musisz posiadać konto Librus Rodzina</string>
|
||||||
<string name="login_mode_librus_synergia">Zaloguj używając loginu i hasła</string>
|
<string name="login_mode_librus_synergia">Zaloguj używając loginu i hasła</string>
|
||||||
<string name="login_mode_librus_synergia_hint">Użyj loginu w postaci \"9874123u\"</string>
|
<string name="login_mode_librus_synergia_hint">Użyj loginu w postaci \"9874123u\"</string>
|
||||||
<string name="login_mode_librus_jst">Logowanie przez platformę VULCAN</string>
|
<string name="login_mode_librus_jst">Logowanie przez platformę VULCAN</string>
|
||||||
<string name="login_mode_librus_jst_hint">Oświata w Radomiu oraz Innowacyjny Tarnobrzeg</string>
|
<string name="login_mode_librus_jst_hint">Oświata w Radomiu oraz Innowacyjny Tarnobrzeg</string>
|
||||||
<string name="login_type_vulcan">Vulcan UONET+</string>
|
<string name="login_type_vulcan" translatable="false">Vulcan UONET+</string>
|
||||||
<string name="login_mode_vulcan_api">Użyj tokenu, symbolu i kodu PIN</string>
|
<string name="login_mode_vulcan_api">Użyj tokenu, symbolu i kodu PIN</string>
|
||||||
<string name="login_mode_vulcan_api_hint">Zarejestruj urządzenie na stronie dziennika Vulcan</string>
|
<string name="login_mode_vulcan_api_hint">Zarejestruj urządzenie na stronie dziennika Vulcan</string>
|
||||||
<string name="login_mode_vulcan_web">Użyj e-maila/nazwy użytkownika i hasła</string>
|
<string name="login_mode_vulcan_web">Użyj e-maila/nazwy użytkownika i hasła</string>
|
||||||
<string name="login_mode_vulcan_web_hint">Zaloguj danymi, które podajesz na stronie e-dziennika VULCAN</string>
|
<string name="login_mode_vulcan_web_hint">Zaloguj danymi, które podajesz na stronie e-dziennika VULCAN</string>
|
||||||
<string name="login_type_mobidziennik">MobiDziennik</string>
|
<string name="login_type_mobidziennik" translatable="false">MobiDziennik</string>
|
||||||
<string name="login_mode_mobidziennik_web">Zaloguj nazwą serwera, loginem i hasłem</string>
|
<string name="login_mode_mobidziennik_web">Zaloguj nazwą serwera, loginem i hasłem</string>
|
||||||
<string name="login_mode_mobidziennik_web_hint">Podaj dane, których używasz na stronie e-dziennika</string>
|
<string name="login_mode_mobidziennik_web_hint">Podaj dane, których używasz na stronie e-dziennika</string>
|
||||||
<string name="login_platform_list_title">W jaki sposób się logujesz do dziennika?</string>
|
<string name="login_platform_list_title">W jaki sposób się logujesz do dziennika?</string>
|
||||||
@ -1316,4 +1316,5 @@
|
|||||||
<string name="login_mode_mobidziennik_web_guide">Podaj dane, których używasz do logowania na stronie MobiDziennika. Jako adres serwera możesz wpisać adres strony internetowej, na której masz MobiDziennik.</string>
|
<string name="login_mode_mobidziennik_web_guide">Podaj dane, których używasz do logowania na stronie MobiDziennika. Jako adres serwera możesz wpisać adres strony internetowej, na której masz MobiDziennik.</string>
|
||||||
<string name="settings_add_student_text">Dodaj nowego ucznia</string>
|
<string name="settings_add_student_text">Dodaj nowego ucznia</string>
|
||||||
<string name="settings_add_student_subtext">Zaloguj konto ucznia/rodzica w aplikacji</string>
|
<string name="settings_add_student_subtext">Zaloguj konto ucznia/rodzica w aplikacji</string>
|
||||||
|
<string name="edziennik_progress_login_vulcan_web_main">Logowanie do dziennika Vulcan...</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user