[APIv2/Librus] Create Login methods, update API structure

This commit is contained in:
Kuba Szczodrzyński 2019-09-21 23:01:31 +02:00
parent 1bdee7857c
commit 76d39ac623
29 changed files with 1258 additions and 505 deletions

View File

@ -47,11 +47,10 @@ public class AppError {
public static final int CODE_LIBRUS_DISCONNECTED = 31; public static final int CODE_LIBRUS_DISCONNECTED = 31;
public static final int CODE_PROFILE_ARCHIVED = 30; public static final int CODE_PROFILE_ARCHIVED = 30;
public static final int CODE_INTERNAL_MISSING_DATA = 100; public static final int CODE_INTERNAL_MISSING_DATA = 100;
// internal errors - not for user's information. // internal errors - not for user's information.
// these error codes are processed in API main classes // these error codes are processed in API main classes
public static final int CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120;
public static final int CODE_INTERNAL_LIBRUS_ACCOUNT_410_ = 120;
public String TAG; public String TAG;
public int line; public int line;

View File

@ -51,7 +51,6 @@ import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback;
import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.api.v2.models.DataStore;
import pl.szczodrzynski.edziennik.datamodels.Announcement; import pl.szczodrzynski.edziennik.datamodels.Announcement;
import pl.szczodrzynski.edziennik.datamodels.Attendance; import pl.szczodrzynski.edziennik.datamodels.Attendance;
import pl.szczodrzynski.edziennik.datamodels.Event; import pl.szczodrzynski.edziennik.datamodels.Event;
@ -213,8 +212,6 @@ public class Librus implements EdziennikInterface {
this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext); this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext);
this.today = Date.getToday(); this.today = Date.getToday();
DataStore ds = new DataStore(app.db, profileId);
this.librusEmail = loginStore.getLoginData("email", ""); this.librusEmail = loginStore.getLoginData("email", "");
this.librusPassword = loginStore.getLoginData("password", ""); this.librusPassword = loginStore.getLoginData("password", "");
if (profile == null) { if (profile == null) {

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2
import com.crashlytics.android.Crashlytics
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.v2.models.Data
open class Api(open val data: Data) {
fun finishWithError(error: AppError) {
try {
data.saveData()
} catch (e: Exception) {
Crashlytics.logException(e)
}
data.callback.onError(null, error)
}
}

View File

@ -16,33 +16,8 @@ const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_OUTBOX = 8 const val FEATURE_MESSAGES_OUTBOX = 8
const val FEATURE_ANNOUNCEMENTS = 9 const val FEATURE_ANNOUNCEMENTS = 9
const val LOGIN_TYPE_MOBIDZIENNIK = 1
const val LOGIN_TYPE_LIBRUS = 2
const val LOGIN_TYPE_IUCZNIOWIE = 3
const val LOGIN_TYPE_VULCAN = 4
const val LOGIN_TYPE_DEMO = 20
// LOGIN MODES
const val LOGIN_MODE_LIBRUS_EMAIL = 0
const val LOGIN_MODE_LIBRUS_SYNERGIA = 1
const val LOGIN_MODE_LIBRUS_JST = 2
const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_VULCAN_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_LIBRUS_PORTAL = 0
const val LOGIN_METHOD_LIBRUS_API = 1
const val LOGIN_METHOD_LIBRUS_SYNERGIA = 2
const val LOGIN_METHOD_LIBRUS_MESSAGES = 3
const val LOGIN_METHOD_MOBIDZIENNIK_API = 0
const val LOGIN_METHOD_IDZIENNIK_WEB = 0
const val LOGIN_METHOD_IDZIENNIK_API = 1
const val LOGIN_METHOD_VULCAN_WEB = 0
const val LOGIN_METHOD_VULCAN_API = 1
const val LIBRUS_USER_AGENT = "Dalvik/2.1.0 Android LibrusMobileApp" const val LIBRUS_USER_AGENT = "Dalvik/2.1.0 Android LibrusMobileApp"
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv" const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"
const val LIBRUS_REDIRECT_URL = "http://localhost/bar" const val LIBRUS_REDIRECT_URL = "http://localhost/bar"
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code" const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
@ -51,3 +26,19 @@ const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
const val LIBRUS_ACCOUNT_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts/fresh/" // + login const val LIBRUS_ACCOUNT_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts/fresh/" // + login
const val LIBRUS_ACCOUNTS_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts" const val LIBRUS_ACCOUNTS_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts"
const val LIBRUS_API_URL = "https://api.librus.pl/2.0/"
const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
const val LIBRUS_API_CLIENT_ID_JST = "49"
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
const val LIBRUS_JST_DEMO_CODE = "68656A21"
const val LIBRUS_JST_DEMO_PIN = "1290"
const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/\$token/przenies/"
const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module/"
const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2
import android.util.Log import android.util.Log
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiGrades import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiGrades
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiMe
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergiaGrades import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergiaGrades
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
@ -13,7 +14,8 @@ const val ENDPOINT_LIBRUS_API_ME = 0
const val ENDPOINT_LIBRUS_API_GRADES = 0 const val ENDPOINT_LIBRUS_API_GRADES = 0
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 0 const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 0
val librusEndpoints = listOf( val endpoints = listOf(
Endpoint(LOGIN_TYPE_LIBRUS, ENDPOINT_LIBRUS_API_ME, null, LibrusApiMe::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API},
Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusSynergiaGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_SYNERGIA }, Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusSynergiaGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_SYNERGIA },
Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusApiGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API } Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusApiGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API }
) )

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2
/*const val CODE_OTHER = 0
const val CODE_OK = 1
const val CODE_NO_INTERNET = 10
const val CODE_SSL_ERROR = 13
const val CODE_ARCHIVED = 5
const val CODE_MAINTENANCE = 6
const val CODE_LOGIN_ERROR = 7
const val CODE_ACCOUNT_MISMATCH = 8
const val CODE_APP_SERVER_ERROR = 9
const val CODE_MULTIACCOUNT_SETUP = 12
const val CODE_TIMEOUT = 11
const val CODE_PROFILE_NOT_FOUND = 14
const val CODE_ATTACHMENT_NOT_AVAILABLE = 28
const val CODE_INVALID_LOGIN = 2
const val CODE_INVALID_SERVER_ADDRESS = 21
const val CODE_INVALID_SCHOOL_NAME = 22
const val CODE_INVALID_DEVICE = 23
const val CODE_OLD_PASSWORD = 4
const val CODE_INVALID_TOKEN = 24
const val CODE_EXPIRED_TOKEN = 27
const val CODE_INVALID_SYMBOL = 25
const val CODE_INVALID_PIN = 26
const val CODE_LIBRUS_NOT_ACTIVATED = 29
const val CODE_SYNERGIA_NOT_ACTIVATED = 32
const val CODE_LIBRUS_DISCONNECTED = 31
const val CODE_PROFILE_ARCHIVED = 30*/
const val CODE_INVALID_LOGIN_MODE = 130
const val CODE_INTERNAL_MISSING_DATA = 100
const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120
const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121
const val CODE_LOGIN_METHOD_NOT_SATISFIED = 122
const val CODE_LIBRUS_PROFILE_NULL = 123
const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124
const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901

View File

@ -4,3 +4,47 @@
package pl.szczodrzynski.edziennik.api.v2 package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusSynergia
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
const val LOGIN_TYPE_MOBIDZIENNIK = 1
const val LOGIN_TYPE_LIBRUS = 2
const val LOGIN_TYPE_IUCZNIOWIE = 3
const val LOGIN_TYPE_VULCAN = 4
const val LOGIN_TYPE_DEMO = 20
// LOGIN MODES
const val LOGIN_MODE_LIBRUS_EMAIL = 0
const val LOGIN_MODE_LIBRUS_SYNERGIA = 1
const val LOGIN_MODE_LIBRUS_JST = 2
const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_VULCAN_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_LIBRUS_PORTAL = 100// 0
const val LOGIN_METHOD_LIBRUS_API = 200// 1
const val LOGIN_METHOD_LIBRUS_SYNERGIA = 300 // 2
const val LOGIN_METHOD_LIBRUS_MESSAGES = 400
const val LOGIN_METHOD_MOBIDZIENNIK_API = 100
const val LOGIN_METHOD_IDZIENNIK_WEB = 100
const val LOGIN_METHOD_IDZIENNIK_API = 200
const val LOGIN_METHOD_VULCAN_WEB = 100
const val LOGIN_METHOD_VULCAN_API = 200
val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, null, LoginLibrusPortal::class.java) { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, null, LoginLibrusApi::class.java) { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, listOf(FEATURE_GRADES, FEATURE_HOMEWORKS, FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX), LoginLibrusSynergia::class.java) { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX), LoginLibrusMessages::class.java) { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
}
)

View File

@ -1,111 +1,31 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context import android.content.Context
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.* import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback
import pl.szczodrzynski.edziennik.api.interfaces.* import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_JST import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_SYNERGIA
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginJst
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor
import pl.szczodrzynski.edziennik.api.v2.models.DataStore
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.MessageFull
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.ProfileFull import pl.szczodrzynski.edziennik.datamodels.ProfileFull
import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo
import pl.szczodrzynski.edziennik.models.Endpoint
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.lang.Exception
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore) : EdziennikInterface { class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: SyncCallback) {
private val TAG = "librus.Librus"
lateinit var syncCallback: SyncCallback val internalErrorList = mutableListOf<Int>()
lateinit var featureList: ArrayList<Int> lateinit var data: DataLibrus
lateinit var dataStore: DataStore
var onLogin: (() -> Unit)? = null init {
val internalErrorList = ArrayList<Int>() data = DataLibrus(app, profile, loginStore).apply {
callback = wrapCallback(this@Librus.callback)
}
fun isError(error: AppError?): Boolean {
if (error == null)
return false
syncCallback.onError(null, error)
return true
}
/* _ _ _
| | (_) |
| | _| |__ _ __ _ _ ___
| | | | '_ \| '__| | | / __|
| |____| | |_) | | | |_| \__ \
|______|_|_.__/|_| \__,_|__*/
private fun loginLibrus() {
LoginLibrus(app, loginStore, syncCallback) {
if (profile == null) {
firstLoginLibrus()
return@LoginLibrus
}
synergiaTokenExtractor()
}
}
private fun firstLoginLibrus() {
FirstLoginLibrus(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
private fun synergiaTokenExtractor() {
if (profile == null) {
throw Exception("Profile may not be null")
}
SynergiaTokenExtractor(app, profile, loginStore, syncCallback) {
d(TAG, "Profile $profile")
d(TAG, "LoginStore $loginStore")
onLogin?.invoke()
}
}
/* _____ _
/ ____| (_)
| (___ _ _ _ __ ___ _ __ __ _ _ __ _
\___ \| | | | '_ \ / _ \ '__/ _` | |/ _` |
____) | |_| | | | | __/ | | (_| | | (_| |
|_____/ \__, |_| |_|\___|_| \__, |_|\__,_|
__/ | __/ |
|___/ |__*/
private fun loginSynergia() {
LoginSynergia(app, loginStore, syncCallback) {
if (profile == null) {
firstLoginSynergia()
return@LoginSynergia
}
onLogin?.invoke()
}
}
private fun firstLoginSynergia() {
FirstLoginSynergia(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
/* _ _____ _______
| |/ ____|__ __|
| | (___ | |
_ | |\___ \ | |
| |__| |____) | | |
\____/|_____/ |*/
private fun loginJst() {
LoginJst(app, null, loginStore, syncCallback) {
if (profile == null) {
firstLoginSynergia()
return@LoginJst
}
onLogin?.invoke()
}
} }
private fun wrapCallback(callback: SyncCallback): SyncCallback { private fun wrapCallback(callback: SyncCallback): SyncCallback {
@ -135,67 +55,11 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore) :
CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> {
internalErrorList.add(error.errorCode) internalErrorList.add(error.errorCode)
loginStore.removeLoginData("refreshToken") // force a clean login loginStore.removeLoginData("refreshToken") // force a clean login
loginLibrus() //loginLibrus()
} }
else -> callback.onError(activityContext, error) else -> callback.onError(activityContext, error)
} }
} }
} }
} }
fun login(callback: SyncCallback) {
this.internalErrorList.clear()
this.syncCallback = wrapCallback(callback)
when (loginStore.mode) {
LOGIN_MODE_LIBRUS_EMAIL -> {
loginLibrus()
}
LOGIN_MODE_LIBRUS_SYNERGIA -> {
}
LOGIN_MODE_LIBRUS_JST -> {
}
}
}
fun getData() {
}
override fun sync(activityContext: Context, callback: SyncCallback, profileId: Int, profile: Profile?, loginStore: LoginStore) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncMessages(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncFeature(activityContext: Context, callback: SyncCallback, profile: ProfileFull, vararg featureList: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getMessage(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, messageCallback: MessageGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getAttachment(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, attachmentId: Long, attachmentCallback: AttachmentGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getRecipientList(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, recipientListGetCallback: RecipientListGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getComposeInfo(profile: ProfileFull): MessagesComposeInfo {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getConfigurableEndpoints(profile: Profile?): MutableMap<String, Endpoint> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isEndpointEnabled(profile: Profile?, defaultActive: Boolean, name: String?): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
} }

View File

@ -0,0 +1,6 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus

View File

@ -0,0 +1,172 @@
package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.interfaces.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.MessageFull
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.ProfileFull
import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo
import pl.szczodrzynski.edziennik.models.Endpoint
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.lang.Exception
class LibrusOld(val app: App, val profile: Profile?, val loginStore: LoginStore) : EdziennikInterface {
private val TAG = "librus.Librus"
lateinit var syncCallback: SyncCallback
lateinit var featureList: ArrayList<Int>
lateinit var data: Data
var onLogin: (() -> Unit)? = null
val internalErrorList = ArrayList<Int>()
fun isError(error: AppError?): Boolean {
if (error == null)
return false
syncCallback.onError(null, error)
return true
}
/* _ _ _
| | (_) |
| | _| |__ _ __ _ _ ___
| | | | '_ \| '__| | | / __|
| |____| | |_) | | | |_| \__ \
|______|_|_.__/|_| \__,_|__*/
private fun firstLoginLibrus() {
FirstLoginLibrus(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
private fun synergiaTokenExtractor() {
if (profile == null) {
throw Exception("Profile may not be null")
}
}
/* _____ _
/ ____| (_)
| (___ _ _ _ __ ___ _ __ __ _ _ __ _
\___ \| | | | '_ \ / _ \ '__/ _` | |/ _` |
____) | |_| | | | | __/ | | (_| | | (_| |
|_____/ \__, |_| |_|\___|_| \__, |_|\__,_|
__/ | __/ |
|___/ |__*/
private fun loginSynergia() {
}
private fun firstLoginSynergia() {
FirstLoginSynergia(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
/* _ _____ _______
| |/ ____|__ __|
| | (___ | |
_ | |\___ \ | |
| |__| |____) | | |
\____/|_____/ |*/
private fun loginJst() {
}
private fun wrapCallback(callback: SyncCallback): SyncCallback {
return object : SyncCallback {
override fun onSuccess(activityContext: Context?, profileFull: ProfileFull?) {
callback.onSuccess(activityContext, profileFull)
}
override fun onProgress(progressStep: Int) {
callback.onProgress(progressStep)
}
override fun onActionStarted(stringResId: Int) {
callback.onActionStarted(stringResId)
}
override fun onLoginFirst(profileList: MutableList<Profile>?, loginStore: LoginStore?) {
callback.onLoginFirst(profileList, loginStore)
}
override fun onError(activityContext: Context?, error: AppError) {
when (error.errorCode) {
in internalErrorList -> {
// finish immediately if the same error occurs twice during the same sync
callback.onError(activityContext, error)
}
/* CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> {
internalErrorList.add(error.errorCode)
loginStore.removeLoginData("refreshToken") // force a clean login
//loginLibrus()
}*/
else -> callback.onError(activityContext, error)
}
}
}
}
fun login(callback: SyncCallback) {
this.internalErrorList.clear()
this.syncCallback = wrapCallback(callback)
when (loginStore.mode) {
LOGIN_MODE_LIBRUS_EMAIL -> {
//loginLibrus()
}
LOGIN_MODE_LIBRUS_SYNERGIA -> {
}
LOGIN_MODE_LIBRUS_JST -> {
}
}
}
fun getData() {
}
override fun sync(activityContext: Context, callback: SyncCallback, profileId: Int, profile: Profile?, loginStore: LoginStore) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncMessages(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncFeature(activityContext: Context, callback: SyncCallback, profile: ProfileFull, vararg featureList: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getMessage(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, messageCallback: MessageGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getAttachment(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, attachmentId: Long, attachmentCallback: AttachmentGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getRecipientList(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, recipientListGetCallback: RecipientListGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getComposeInfo(profile: ProfileFull): MessagesComposeInfo {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getConfigurableEndpoints(profile: Profile?): MutableMap<String, Endpoint> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isEndpointEnabled(profile: Profile?, defaultActive: Boolean, name: String?): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusPortal
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class LibrusTest(val app: App) {
companion object {
private const val TAG = "LibrusTest"
}
val profile = Profile(1, "Profil", "xd", 1).apply {
//putStudentData("accountLogin", "1234567")
//putStudentData("accountPassword", "zaq1@WSX")
putStudentData("accountCode", LIBRUS_JST_DEMO_CODE)
putStudentData("accountPin", LIBRUS_JST_DEMO_PIN)
}
val loginStore = LoginStore(1, LOGIN_TYPE_LIBRUS, JsonObject().apply {
addProperty("email", "test@example.com")
addProperty("password", "zaq1@WSX")
}).also {
it.mode = LOGIN_MODE_LIBRUS_JST
}
fun go() {
val data = DataLibrus(app, profile, loginStore).apply {
callback = object : ProgressCallback {
override fun onProgress(progressStep: Int) {
}
override fun onActionStarted(stringResId: Int) {
d(TAG, app.getString(stringResId))
}
override fun onError(activityContext: Context?, error: AppError) {
error.changeIfCodeOther()
d(TAG, "Error "+error.getDetails(app))
}
}
}
LoginLibrus(data, LOGIN_METHOD_LIBRUS_API) {
d(TAG, "Login succeeded.")
d(TAG, "Profile data: ${data.profile?.studentData?.toString()}")
d(TAG, "LoginStore data: ${data.loginStore.data}")
}
}
}

View File

@ -1,10 +1,122 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
class DataLibrus(val app: App, val profile: Profile, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
/* _____ _ _
| __ \ | | | |
| |__) |__ _ __| |_ __ _| |
| ___/ _ \| '__| __/ _` | |
| | | (_) | | | || (_| | |
|_| \___/|_| \__\__,_|*/
private var mPortalEmail: String? = null
var portalEmail: String?
get() { mPortalEmail = mPortalEmail ?: loginStore.getLoginData("email", null); return mPortalEmail }
set(value) { loginStore.putLoginData("email", value); mPortalEmail = value }
private var mPortalPassword: String? = null
var portalPassword: String?
get() { mPortalPassword = mPortalPassword ?: loginStore.getLoginData("password", null); return mPortalPassword }
set(value) { loginStore.putLoginData("password", value); mPortalPassword = value }
private var mPortalAccessToken: String? = null
var portalAccessToken: String?
get() { mPortalAccessToken = mPortalAccessToken ?: loginStore.getLoginData("accessToken", null); return mPortalAccessToken }
set(value) { loginStore.putLoginData("accessToken", value); mPortalAccessToken = value }
private var mPortalRefreshToken: String? = null
var portalRefreshToken: String?
get() { mPortalRefreshToken = mPortalRefreshToken ?: loginStore.getLoginData("refreshToken", null); return mPortalRefreshToken }
set(value) { loginStore.putLoginData("refreshToken", value); mPortalRefreshToken = value }
private var mPortalTokenExpiryTime: Long? = null
var portalTokenExpiryTime: Long
get() { mPortalTokenExpiryTime = mPortalTokenExpiryTime ?: loginStore.getLoginData("tokenExpiryTime", 0L); return mPortalTokenExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("tokenExpiryTime", value); mPortalTokenExpiryTime = value }
/* _____ _____
/\ | __ \_ _|
/ \ | |__) || |
/ /\ \ | ___/ | |
/ ____ \| | _| |_
/_/ \_\_| |____*/
/**
* A Synergia login, like 1234567u.
* Used: for login (API Login Method) in Synergia mode.
* And also in various places in [pl.szczodrzynski.edziennik.api.v2.models.Endpoint]s
*/
private var mApiLogin: String? = null
var apiLogin: String?
get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin }
set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value }
/**
* A Synergia password.
* Used: for login (API Login Method) in Synergia mode.
*/
private var mApiPassword: String? = null
var apiPassword: String?
get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword }
set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value }
/**
* A JST login Code.
* Used only during first login in JST mode.
*/
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: profile?.getStudentData("accountCode", null); return mApiCode }
set(value) { profile?.putStudentData("accountCode", value) ?: return; mApiCode = value }
/**
* A JST login PIN.
* Used only during first login in JST mode.
*/
private var mApiPin: String? = null
var apiPin: String?
get() { mApiPin = mApiPin ?: profile?.getStudentData("accountPin", null); return mApiPin }
set(value) { profile?.putStudentData("accountPin", value) ?: return; mApiPin = value }
/**
* A Synergia API access token.
* Used in all Api Endpoints.
* Created in Login Method Api.
* Applicable for all login modes.
*/
private var mApiAccessToken: String? = null
var apiAccessToken: String?
get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken }
set(value) { profile?.putStudentData("accountToken", value) ?: return; mApiAccessToken = value }
/**
* A Synergia API refresh token.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
*/
private var mApiRefreshToken: String? = null
var apiRefreshToken: String?
get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken }
set(value) { profile?.putStudentData("accountRefreshToken", value) ?: return; mApiRefreshToken = value }
/**
* The expiry time for [apiAccessToken], as a UNIX timestamp.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
* Used when refreshing the [apiAccessToken] in Portal mode ([pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor])
*/
private var mApiTokenExpiryTime: Long? = null
var apiTokenExpiryTime: Long
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
set(value) { profile?.putStudentData("accountTokenTime", value) ?: return; mApiTokenExpiryTime = value }
/* ____ _ _
/ __ \| | | |
| | | | |_| |__ ___ _ __
| | | | __| '_ \ / _ \ '__|
| |__| | |_| | | | __/ |
\____/ \__|_| |_|\___|*/
var isPremium
get() = profile?.getStudentData("isPremium", false) ?: false
set(value) { profile?.putStudentData("isPremium", value) }
} }

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.CODE_MAINTENANCE
import pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER
import pl.szczodrzynski.edziennik.api.v2.Api
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_API_URL
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_USER_AGENT
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
open class LibrusApi(override val data: DataLibrus) : Api(data) {
companion object {
const val TAG = "LibrusApi"
}
fun apiRequest(endpoint: String, callback: (json: JsonObject?) -> Unit) {
d(TAG, "Requesting $LIBRUS_API_URL$endpoint")
Request.builder()
.url(if (data.fakeLogin) "http://szkolny.eu/librus/api/$endpoint" else LIBRUS_API_URL + endpoint)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiAccessToken}")
.get()
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_BAD_REQUEST)
.callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
if (response.parserErrorBody != null && response.parserErrorBody == "Nieprawidłowy węzeł.") {
callback(null)
return
}
finishWithError(AppError(TAG, 453, CODE_MAINTENANCE, response))
return
}
if (json.get("Status") != null) {
val message = json.get("Message")
val code = json.get("Code")
d(TAG, "apiRequest Error " + json.get("Status").asString + " " + (if (message == null) "" else message.asString) + " " + (if (code == null) "" else code.asString) + "\n\n" + response.request().url().toString())
if (message != null && message !is JsonNull && message.asString == "Student timetable is not public") {
try {
callback(null)
} catch (e: NullPointerException) {
e.printStackTrace()
d(TAG, "apiRequest exception " + e.message)
finishWithError(AppError(TAG, 503, CODE_OTHER, response, e, json))
}
return
}
if (code != null
&& code !is JsonNull
&& (code.asString == "LuckyNumberIsNotActive"
|| code.asString == "NotesIsNotActive"
|| code.asString == "AccessDeny")) {
try {
callback(null)
} catch (e: NullPointerException) {
e.printStackTrace()
d(TAG, "apiRequest exception " + e.message)
finishWithError(AppError(TAG, 504, CODE_OTHER, response, e, json))
}
return
}
val errorText = json.get("Status").asString + " " + (if (message == null) "" else message.asString) + " " + if (code == null) "" else code.asString
if (code != null && code !is JsonNull && code.asString == "TokenIsExpired") {
finishWithError(AppError(TAG, 74, CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED, errorText, response, json))
return
}
finishWithError(AppError(TAG, 497, CODE_OTHER, errorText, response, json))
return
}
try {
callback(json)
} catch (e: NullPointerException) {
e.printStackTrace()
d(TAG, "apiRequest exception " + e.message)
finishWithError(AppError(TAG, 505, CODE_OTHER, response, e, json))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 405) {
// method not allowed
finishWithError(AppError(TAG, 511, CODE_OTHER, response, throwable))
return
}
if (response.code() == 500) {
// TODO: 2019-09-10 dirty hotfix
if ("Classrooms" == endpoint) {
callback(null)
return
}
finishWithError(AppError(TAG, 516, CODE_MAINTENANCE, response, throwable))
return
}
finishWithError(AppError(TAG, 520, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
}

View File

@ -2,19 +2,16 @@ package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.DataStore import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
class LibrusApiGrades(val app: App, class LibrusApiGrades(val app: App,
val profile: Profile, val profile: Profile,
val loginStore: LoginStore, val loginStore: LoginStore,
val dataStore: DataStore, val data: Data,
val callback: ProgressCallback, val callback: ProgressCallback,
val onSuccess: () -> Unit) : EndpointInterface { val onSuccess: () -> Unit) {
override fun sync() {
}
init { init {

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class LibrusApiMe(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
init {
apiRequest("Me") { json ->
val me = json?.getJsonObject("Me")
val account = me?.getJsonObject("Account")
val user = me?.getJsonObject("User")
data.isPremium = account?.getBoolean("isPremium") == true || account?.getBoolean("isPremiumDemo") == true
val isParent = account?.getInt("GroupId") == 5
data.profile?.accountNameLong =
if (isParent)
buildFullName(account?.getString("FirstName"), account?.getString("LastName"))
else null
data.profile?.studentNameLong =
buildFullName(user?.getString("FirstName"), user?.getString("LastName"))
onSuccess()
}
}
}

View File

@ -2,16 +2,16 @@ package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.DataStore import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
class LibrusSynergiaGrades(val app: App, class LibrusSynergiaGrades(val app: App,
val profile: Profile, val profile: Profile,
val loginStore: LoginStore, val loginStore: LoginStore,
val dataStore: DataStore, val data: Data,
val callback: ProgressCallback, val callback: ProgressCallback,
val onSuccess: () -> Unit) { val onSuccess: () -> Unit) {
init { init {

View File

@ -1,20 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.ILoginMethod
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class LoginJst(
app: App,
profile: Profile?,
loginStore: LoginStore,
callback: ProgressCallback,
onSuccess: () -> Unit
): ILoginMethod(app, profile, loginStore, callback, onSuccess) {
companion object {
private const val TAG = "librus.LoginJst"
}
}

View File

@ -1,183 +1,79 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login package pl.szczodrzynski.edziennik.api.v2.librus.login
import android.util.Pair
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.getInt import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.c import kotlin.math.log
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
import java.util.ArrayList
import java.util.regex.Pattern
class LoginLibrus(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { class LoginLibrus(val data: DataLibrus, vararg loginMethodIds: Int, val onSuccess: () -> Unit) {
companion object { companion object {
private const val TAG = "librus.LoginLibrus" private const val TAG = "LoginLibrus"
} }
private var loginMethodList = mutableListOf<Int>()
init { init {
// ustawiamy tokeny, generujemy itp for (loginMethodId in loginMethodIds) {
// nic nie robimy z dostępem do api.librus.pl var requiredLoginMethod = loginMethodId
// to będzie później while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) {
val accessToken = loginStore.getLoginData("accessToken", null) librusLoginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod ->
val refreshToken = loginStore.getLoginData("refreshToken", null) loginMethodList.add(requiredLoginMethod)
val tokenExpiryTime = loginStore.getLoginData("tokenExpiryTime", 0L) requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore)
}
}
}
loginMethodList = loginMethodList.toHashSet().toMutableList()
loginMethodList.sort()
// succeed having a non-expired access token and a refresh token nextLoginMethod()
if (tokenExpiryTime-30 > System.currentTimeMillis() / 1000 && refreshToken != null && accessToken != null) { }
private fun nextLoginMethod() {
if (loginMethodList.isEmpty()) {
onSuccess() onSuccess()
return
} }
else if (refreshToken != null) { useLoginMethod(loginMethodList.removeAt(0)) {
app.cookieJar.clearForDomain("portal.librus.pl") nextLoginMethod()
accessToken(null, refreshToken)
}
else {
app.cookieJar.clearForDomain("portal.librus.pl")
authorize(LIBRUS_AUTHORIZE_URL)
} }
} }
private fun authorize(url: String?) { private fun useLoginMethod(loginMethodId: Int, onSuccess: () -> Unit) {
callback.onActionStarted(R.string.sync_action_authorizing) if (data.loginMethods.contains(loginMethodId)) {
Request.builder() onSuccess()
.url(url) return
.userAgent(LIBRUS_USER_AGENT) }
.withClient(app.httpLazy) d(TAG, "Using login method $loginMethodId")
.callback(object : TextCallbackHandler() { when (loginMethodId) {
override fun onSuccess(data: String, response: Response) { LOGIN_METHOD_LIBRUS_PORTAL -> {
//d("headers "+response.headers().toString()); LoginLibrusPortal(data) {
val location = response.headers().get("Location") data.loginMethods.add(loginMethodId)
if (location != null) { onSuccess()
val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) }
if (authMatcher.find()) { }
accessToken(authMatcher.group(1), null) LOGIN_METHOD_LIBRUS_API -> {
} else { LoginLibrusApi(data) {
//callback.onError(activityContext, Edziennik.CODE_OTHER, "Auth code not found: "+location); data.loginMethods.add(loginMethodId)
authorize(location) onSuccess()
} }
} else { }
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(data) LOGIN_METHOD_LIBRUS_SYNERGIA -> {
if (csrfMatcher.find()) { LoginLibrusApi(data) {
login(csrfMatcher.group(1)) data.loginMethods.add(loginMethodId)
} else { onSuccess()
callback.onError(null, AppError(TAG, 463, CODE_OTHER, "CSRF token not found.", response, data)) }
} }
} LOGIN_METHOD_LIBRUS_MESSAGES -> {
} LoginLibrusApi(data) {
data.loginMethods.add(loginMethodId)
override fun onFailure(response: Response, throwable: Throwable) { onSuccess()
callback.onError(null, AppError(TAG, 207, CODE_OTHER, response, throwable)) }
} }
})
.build()
.enqueue()
}
private fun login(csrfToken: String) {
callback.onActionStarted(R.string.sync_action_logging_in)
val email = loginStore.getLoginData("email", "")
val password = loginStore.getLoginData("password", "")
Request.builder()
.url(LIBRUS_LOGIN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("email", email)
.addParameter("password", password)
.addHeader("X-CSRF-TOKEN", csrfToken)
.contentType(MediaTypeUtils.APPLICATION_JSON)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(data: JsonObject?, response: Response) {
if (data == null) {
if (response.parserErrorBody != null && response.parserErrorBody.contains("wciąż nieaktywne")) {
callback.onError(null, AppError(TAG, 487, CODE_LIBRUS_NOT_ACTIVATED, response))
}
callback.onError(null, AppError(TAG, 489, CODE_MAINTENANCE, response))
return
}
if (data.get("errors") != null) {
callback.onError(null, AppError(TAG, 490, CODE_OTHER, data.get("errors").asJsonArray.get(0).asString, response, data))
return
}
authorize(data.getString("redirect") ?: LIBRUS_AUTHORIZE_URL)
}
override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) {
callback.onError(null, AppError(TAG, 248, CODE_INVALID_LOGIN, response, throwable))
return
}
callback.onError(null, AppError(TAG, 251, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private var refreshTokenFailed = false
private fun accessToken(code: String?, refreshToken: String?) {
callback.onActionStarted(R.string.sync_action_getting_token)
val params = ArrayList<Pair<String, Any>>()
params.add(Pair("client_id", LIBRUS_CLIENT_ID))
if (code != null) {
params.add(Pair("grant_type", "authorization_code"))
params.add(Pair("code", code))
params.add(Pair("redirect_uri", LIBRUS_REDIRECT_URL))
} else if (refreshToken != null) {
params.add(Pair("grant_type", "refresh_token"))
params.add(Pair("refresh_token", refreshToken))
} }
Request.builder()
.url(LIBRUS_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParams(params)
.allowErrorCode(HTTP_UNAUTHORIZED)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(data: JsonObject?, response: Response) {
if (data == null) {
callback.onError(null, AppError(TAG, 539, CODE_MAINTENANCE, response))
return
}
if (data.get("error") != null) {
val hint = data.getString("hint")
if (!refreshTokenFailed && refreshToken != null && (hint == "Token has been revoked" || hint == "Token has expired")) {
c(TAG, "refreshing the token failed. Trying to log in again.")
refreshTokenFailed = true
authorize(LIBRUS_AUTHORIZE_URL)
return
}
val errorText = data.getString("error") + " " + (data.getString("message") ?: "") + " " + (hint ?: "")
callback.onError(null, AppError(TAG, 552, CODE_OTHER, errorText, response, data))
return
}
try {
loginStore.putLoginData("tokenType", data.getString("token_type"))
loginStore.putLoginData("accessToken", data.getString("access_token"))
loginStore.putLoginData("refreshToken", data.getString("refresh_token"))
loginStore.putLoginData("tokenExpiryTime", System.currentTimeMillis() / 1000 + (data.getInt("expires_in") ?: 86400))
onSuccess()
} catch (e: NullPointerException) {
callback.onError(null, AppError(TAG, 311, CODE_OTHER, response, e, data))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
callback.onError(null, AppError(TAG, 317, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
} }
} }

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import java.net.HttpURLConnection
import java.net.HttpURLConnection.*
class LoginLibrusApi {
companion object {
private const val TAG = "LoginLibrusApi"
}
private lateinit var data: DataLibrus
private lateinit var onSuccess: () -> Unit
constructor(data: DataLibrus, onSuccess: () -> Unit) {
this.data = data
this.onSuccess = onSuccess
if (data.profile == null) {
data.callback.onError(null, AppError(TAG, 19, CODE_LIBRUS_PROFILE_NULL))
return
}
if (data.apiTokenExpiryTime-30 > currentTimeUnix() && data.apiAccessToken.isNotNullNorEmpty()) {
onSuccess()
}
else {
when (data.loginStore.mode) {
LOGIN_MODE_LIBRUS_EMAIL -> loginWithPortal()
LOGIN_MODE_LIBRUS_SYNERGIA -> loginWithSynergia()
LOGIN_MODE_LIBRUS_JST -> loginWithJst()
else -> {
data.callback.onError(null, AppError(TAG, 25, CODE_INVALID_LOGIN_MODE))
}
}
}
}
private fun loginWithPortal() {
if (!data.loginMethods.contains(LOGIN_METHOD_LIBRUS_PORTAL)) {
data.callback.onError(null, AppError(TAG, 26, CODE_LOGIN_METHOD_NOT_SATISFIED))
return
}
SynergiaTokenExtractor(data) {
onSuccess()
}
}
private fun copyFromLoginStore() {
data.loginStore.data?.apply {
if (has("accountLogin")) {
data.apiLogin = getString("accountLogin")
remove("accountLogin")
}
if (has("accountPassword")) {
data.apiPassword = getString("accountPassword")
remove("accountPassword")
}
if (has("accountCode")) {
data.apiCode = getString("accountCode")
remove("accountCode")
}
if (has("accountPin")) {
data.apiPin = getString("accountPin")
remove("accountPin")
}
}
}
private fun loginWithSynergia() {
copyFromLoginStore()
if (data.apiRefreshToken != null) {
// refresh a Synergia token
synergiaRefreshToken()
}
else if (data.apiLogin != null && data.apiPassword != null) {
synergiaGetToken()
}
else {
// cannot log in: token expired, no login data present
data.callback.onError(null, AppError(TAG, 91, CODE_INVALID_LOGIN))
}
}
private fun loginWithJst() {
copyFromLoginStore()
if (data.apiRefreshToken != null) {
// refresh a JST token
jstRefreshToken()
}
else if (data.apiCode != null && data.apiPin != null) {
// get a JST token from Code and PIN
jstGetToken()
}
else {
// cannot log in: token expired, no login data present
data.callback.onError(null, AppError(TAG, 110, CODE_INVALID_LOGIN))
}
}
private val tokenCallback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.callback.onError(null, AppError(TAG, 117, CODE_MAINTENANCE, response))
return
}
json.getString("error")?.let { error ->
when (error) {
"librus_captcha_needed" -> {
}
"connection_problems" -> {
}
"invalid_client" -> {
}
"librus_reg_accept_needed" -> {
}
"librus_change_password_error" -> {
}
"librus_password_change_required" -> {
}
"invalid_grant" -> {
}
else -> {
}
}
return
}
try {
data.apiAccessToken = json.getString("access_token")
data.apiRefreshToken = json.getString("refresh_token")
data.apiTokenExpiryTime = currentTimeUnix() + json.getInt("expires_in", 86400)
onSuccess()
} catch (e: NullPointerException) {
data.callback.onError(null, AppError(TAG, 154, EXCEPTION_LOGIN_LIBRUS_API_TOKEN, response, e, json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.callback.onError(null, AppError(TAG, 159, CODE_OTHER, response, throwable))
}
}
private fun synergiaGetToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "password")
.addParameter("username", data.apiLogin)
.addParameter("password", data.apiPassword)
.addParameter("librus_long_term_token", "1")
.addParameter("librus_rules_accepted", "1")
.addHeader("Authorization", "Basic $LIBRUS_API_AUTHORIZATION")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
private fun synergiaRefreshToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "refresh_token")
.addParameter("refresh_token", data.apiRefreshToken)
.addParameter("librus_long_term_token", "1")
.addParameter("librus_rules_accepted", "1")
.addHeader("Authorization", "Basic $LIBRUS_API_AUTHORIZATION")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
private fun jstGetToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_JST_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "implicit_grant")
.addParameter("client_id", LIBRUS_API_CLIENT_ID_JST)
.addParameter("secret", LIBRUS_API_SECRET_JST)
.addParameter("code", data.apiCode)
.addParameter("pin", data.apiPin)
.addParameter("librus_rules_accepted", "1")
.addParameter("librus_mobile_rules_accepted", "1")
.addParameter("librus_long_term_token", "1")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
private fun jstRefreshToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_JST_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "refresh_token")
.addParameter("client_id", LIBRUS_API_CLIENT_ID_JST)
.addParameter("refresh_token", data.apiRefreshToken)
.addParameter("librus_long_term_token", "1")
.addParameter("mobile_app_accept_rules", "1")
.addParameter("synergy_accept_rules", "1")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,8 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login
class LoginLibrusMessages {
}

View File

@ -0,0 +1,179 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import android.util.Pair
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.utils.Utils.c
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
import java.util.ArrayList
import java.util.regex.Pattern
class LoginLibrusPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "LoginLibrusPortal"
}
init { run {
if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) {
data.callback.onError(null, AppError(TAG, 27, CODE_INVALID_LOGIN_MODE))
return@run
}
if (data.portalEmail == null || data.portalPassword == null) {
data.callback.onError(null, AppError(TAG, 31, CODE_INVALID_LOGIN))
return@run
}
// succeed having a non-expired access token and a refresh token
if (data.portalTokenExpiryTime-30 > currentTimeUnix() && data.portalRefreshToken.isNotNullNorEmpty() && data.portalAccessToken.isNotNullNorEmpty()) {
onSuccess()
}
else if (data.portalRefreshToken != null) {
data.app.cookieJar.clearForDomain("portal.librus.pl")
accessToken(null, data.portalRefreshToken)
}
else {
data.app.cookieJar.clearForDomain("portal.librus.pl")
authorize(LIBRUS_AUTHORIZE_URL)
}
}}
private fun authorize(url: String?) {
data.callback.onActionStarted(R.string.sync_action_authorizing)
Request.builder()
.url(url)
.userAgent(LIBRUS_USER_AGENT)
.withClient(data.app.httpLazy)
.callback(object : TextCallbackHandler() {
override fun onSuccess(json: String, response: Response) {
val location = response.headers().get("Location")
if (location != null) {
val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
if (authMatcher.find()) {
accessToken(authMatcher.group(1), null)
} else {
authorize(location)
}
} else {
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(json)
if (csrfMatcher.find()) {
login(csrfMatcher.group(1))
} else {
data.callback.onError(null, AppError(TAG, 463, CODE_OTHER, "CSRF token not found.", response, json))
}
}
}
override fun onFailure(response: Response, throwable: Throwable) {
data.callback.onError(null, AppError(TAG, 207, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private fun login(csrfToken: String) {
data.callback.onActionStarted(R.string.sync_action_logging_in)
Request.builder()
.url(LIBRUS_LOGIN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("email", data.portalEmail)
.addParameter("password", data.portalPassword)
.addHeader("X-CSRF-TOKEN", csrfToken)
.contentType(MediaTypeUtils.APPLICATION_JSON)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
data.callback.onError(null, AppError(TAG, 487, CODE_LIBRUS_NOT_ACTIVATED, response))
return
}
data.callback.onError(null, AppError(TAG, 489, CODE_MAINTENANCE, response))
return
}
if (json.get("errors") != null) {
data.callback.onError(null, AppError(TAG, 490, CODE_OTHER, json.getJsonArray("errors")?.get(0)?.asString, response, json))
return
}
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
}
override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) {
data.callback.onError(null, AppError(TAG, 248, CODE_INVALID_LOGIN, response, throwable))
return
}
data.callback.onError(null, AppError(TAG, 251, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private var refreshTokenFailed = false
private fun accessToken(code: String?, refreshToken: String?) {
data.callback.onActionStarted(R.string.sync_action_getting_token)
val params = ArrayList<Pair<String, Any>>()
params.add(Pair("client_id", LIBRUS_CLIENT_ID))
if (code != null) {
params.add(Pair("grant_type", "authorization_code"))
params.add(Pair("code", code))
params.add(Pair("redirect_uri", LIBRUS_REDIRECT_URL))
} else if (refreshToken != null) {
params.add(Pair("grant_type", "refresh_token"))
params.add(Pair("refresh_token", refreshToken))
}
Request.builder()
.url(LIBRUS_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParams(params)
.allowErrorCode(HTTP_UNAUTHORIZED)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
data.callback.onError(null, AppError(TAG, 539, CODE_MAINTENANCE, response))
return
}
json.getString("error")?.let { error ->
val hint = json.getString("hint", "")
val message = json.getString("message", "")
if (!refreshTokenFailed && refreshToken != null && (hint == "Token has been revoked" || hint == "Token has expired")) {
c(TAG, "refreshing the token failed. Trying to log in again.")
refreshTokenFailed = true
authorize(LIBRUS_AUTHORIZE_URL)
return
}
val errorText = "$error $message $hint"
data.callback.onError(null, AppError(TAG, 552, CODE_OTHER, errorText, response, json))
return
}
try {
data.portalAccessToken = json.getString("access_token")
data.portalRefreshToken = json.getString("refresh_token")
data.portalTokenExpiryTime = currentTimeUnix() + json.getInt("expires_in", 86400)
onSuccess()
} catch (e: NullPointerException) {
data.callback.onError(null, AppError(TAG, 311, CODE_OTHER, response, e, json))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
data.callback.onError(null, AppError(TAG, 317, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
}

View File

@ -0,0 +1,8 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login
class LoginLibrusSynergia {
}

View File

@ -1,12 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.datamodels.LoginStore
class LoginSynergia(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "librus.LoginSynergia"
}
}

View File

@ -8,41 +8,43 @@ import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.* import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_USER_AGENT import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNT_URL
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.* import java.net.HttpURLConnection.*
class SynergiaTokenExtractor(val app: App, val profile: Profile, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object { companion object {
private const val TAG = "librus.SynergiaToken" private const val TAG = "librus.SynergiaToken"
} }
init { init { run {
val accountToken = profile.getStudentData("accountToken", null) if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) {
val accountTokenTime = profile.getStudentData("accountTokenTime", 0L) data.callback.onError(null, AppError(TAG, 23, CODE_INVALID_LOGIN_MODE))
if (accountToken.isNotNullNorEmpty() && currentTimeUnix() - accountTokenTime < 3 * 60 * 60) { return@run
}
if (data.profile == null) {
data.callback.onError(null, AppError(TAG, 28, CODE_LIBRUS_PROFILE_NULL))
return@run
}
if (data.apiTokenExpiryTime-30 > currentTimeUnix() && data.apiAccessToken.isNotNullNorEmpty()) {
onSuccess() onSuccess()
} }
else { else {
if (!synergiaAccount()) synergiaAccount()
callback.onError(null, AppError(TAG, 33, CODE_INTERNAL_MISSING_DATA))
} }
} }}
private fun synergiaAccount(): Boolean { private fun synergiaAccount(): Boolean {
val accountLogin = profile.getStudentData("accountLogin", null) ?: return false val accountLogin = data.apiLogin ?: return false
val tokenType = loginStore.getLoginData("tokenType", null) ?: return false val accessToken = data.portalAccessToken ?: return false
val accessToken = loginStore.getLoginData("accessToken", null) ?: return false data.callback.onActionStarted(R.string.sync_action_getting_account)
callback.onActionStarted(R.string.sync_action_getting_account)
d(TAG, "Requesting " + (LIBRUS_ACCOUNT_URL + accountLogin)) d(TAG, "Requesting " + (LIBRUS_ACCOUNT_URL + accountLogin))
Request.builder() Request.builder()
.url(LIBRUS_ACCOUNT_URL + accountLogin) .url(LIBRUS_ACCOUNT_URL + accountLogin)
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "$tokenType $accessToken") .addHeader("Authorization", "Bearer $accessToken")
.get() .get()
.allowErrorCode(HTTP_NOT_FOUND) .allowErrorCode(HTTP_NOT_FOUND)
.allowErrorCode(HTTP_FORBIDDEN) .allowErrorCode(HTTP_FORBIDDEN)
@ -50,57 +52,56 @@ class SynergiaTokenExtractor(val app: App, val profile: Profile, val loginStore:
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_GONE) .allowErrorCode(HTTP_GONE)
.callback(object : JsonCallbackHandler() { .callback(object : JsonCallbackHandler() {
override fun onSuccess(data: JsonObject?, response: Response) { override fun onSuccess(json: JsonObject?, response: Response) {
if (data == null) { if (json == null) {
callback.onError(null, AppError(TAG, 641, CODE_MAINTENANCE, response)) data.callback.onError(null, AppError(TAG, 641, CODE_MAINTENANCE, response))
return return
} }
if (response.code() == 410) { if (response.code() == 410) {
val reason = data.get("reason") val reason = json.get("reason")
if (reason != null && reason !is JsonNull && reason.asString == "requires_an_action") { if (reason != null && reason !is JsonNull && reason.asString == "requires_an_action") {
callback.onError(null, AppError(TAG, 1078, CODE_LIBRUS_DISCONNECTED, response, data)) data.callback.onError(null, AppError(TAG, 1078, CODE_LIBRUS_DISCONNECTED, response, json))
return return
} }
callback.onError(null, AppError(TAG, 70, CODE_INTERNAL_LIBRUS_ACCOUNT_410)) data.callback.onError(null, AppError(TAG, 70, CODE_INTERNAL_LIBRUS_ACCOUNT_410))
return return
} }
if (data.get("message") != null) { if (json.get("message") != null) {
val message = data.get("message").asString val message = json.get("message").asString
if (message == "Account not found") { if (message == "Account not found") {
callback.onError(null, AppError(TAG, 651, CODE_OTHER, app.getString(R.string.sync_error_register_student_not_associated_format, profile.studentNameLong, accountLogin), response, data)) data.callback.onError(null, AppError(TAG, 651, CODE_OTHER, data.app.getString(R.string.sync_error_register_student_not_associated_format, data.profile?.studentNameLong ?: "", accountLogin), response, json))
return return
} }
callback.onError(null, AppError(TAG, 654, CODE_OTHER, message + "\n\n" + accountLogin, response, data)) data.callback.onError(null, AppError(TAG, 654, CODE_OTHER, message + "\n\n" + accountLogin, response, json))
return return
} }
if (response.code() == HTTP_OK) { if (response.code() == HTTP_OK) {
try { try {
// synergiaAccount is executed when a synergia token needs a refresh // synergiaAccount is executed when a synergia token needs a refresh
val accountId = data.getInt("id") val accountId = json.getInt("id")
val accountToken = data.getString("accessToken") val accountToken = json.getString("accessToken")
if (accountId == null || accountToken == null) { if (accountId == null || accountToken == null) {
callback.onError(null, AppError(TAG, 1284, CODE_OTHER, data)) data.callback.onError(null, AppError(TAG, 1284, CODE_OTHER, json))
return return
} }
profile.putStudentData("accountId", accountId) data.apiAccessToken = accountToken
profile.putStudentData("accountToken", accountToken) data.apiTokenExpiryTime = currentTimeUnix() + 6*60*60
profile.putStudentData("accountTokenTime", System.currentTimeMillis() / 1000) data.profile?.studentNameLong = json.getString("studentName")
profile.studentNameLong = data.getString("studentName") val nameParts = json.getString("studentName")?.split(" ")?.toTypedArray()
val nameParts = data.getString("studentName")?.split(" ")?.toTypedArray() data.profile?.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
profile.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
onSuccess() onSuccess()
} catch (e: NullPointerException) { } catch (e: NullPointerException) {
e.printStackTrace() e.printStackTrace()
callback.onError(null, AppError(TAG, 662, CODE_OTHER, response, e, data)) data.callback.onError(null, AppError(TAG, 662, CODE_OTHER, response, e, json))
} }
} else { } else {
callback.onError(null, AppError(TAG, 425, CODE_OTHER, response, data)) data.callback.onError(null, AppError(TAG, 425, CODE_OTHER, response, json))
} }
} }
override fun onFailure(response: Response, throwable: Throwable) { override fun onFailure(response: Response, throwable: Throwable) {
callback.onError(null, AppError(TAG, 432, CODE_OTHER, response, throwable)) data.callback.onError(null, AppError(TAG, 432, CODE_OTHER, response, throwable))
} }
}) })
.build() .build()

View File

@ -3,15 +3,21 @@ package pl.szczodrzynski.edziennik.api.v2.models
import android.util.LongSparseArray import android.util.LongSparseArray
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.core.util.isNotEmpty import androidx.core.util.isNotEmpty
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.datamodels.* import pl.szczodrzynski.edziennik.datamodels.*
import pl.szczodrzynski.edziennik.models.Date import pl.szczodrzynski.edziennik.models.Date
data class DataStore(private val appDb: AppDb, val profileId: Int) { open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) {
var fakeLogin = false
lateinit var callback: ProgressCallback
val loginMethods = mutableListOf<Int>() val loginMethods = mutableListOf<Int>()
val teacherList: LongSparseArray<Teacher> = LongSparseArray() val teacherList = LongSparseArray<Teacher>()
val subjectList: LongSparseArray<Subject> = LongSparseArray() val subjectList = LongSparseArray<Subject>()
val teamList = mutableListOf<Team>() val teamList = mutableListOf<Team>()
val lessonList = mutableListOf<Lesson>() val lessonList = mutableListOf<Lesson>()
val lessonChangeList = mutableListOf<LessonChange>() val lessonChangeList = mutableListOf<LessonChange>()
@ -28,15 +34,19 @@ data class DataStore(private val appDb: AppDb, val profileId: Int) {
val metadataList = mutableListOf<Metadata>() val metadataList = mutableListOf<Metadata>()
val messageMetadataList = mutableListOf<Metadata>() val messageMetadataList = mutableListOf<Metadata>()
private val db by lazy { app.db }
init { init {
clear() clear()
appDb.teacherDao().getAllNow(profileId).forEach { teacher -> if (profile != null) {
teacherList.put(teacher.id, teacher) db.teacherDao().getAllNow(profile.id).forEach { teacher ->
} teacherList.put(teacher.id, teacher)
appDb.subjectDao().getAllNow(profileId).forEach { subject -> }
subjectList.put(subject.id, subject) db.subjectDao().getAllNow(profile.id).forEach { subject ->
subjectList.put(subject.id, subject)
}
} }
/*val teacher = teachers.byNameFirstLast("Jan Kowalski") ?: Teacher(1, 1, "", "").let { /*val teacher = teachers.byNameFirstLast("Jan Kowalski") ?: Teacher(1, 1, "", "").let {
@ -67,57 +77,60 @@ data class DataStore(private val appDb: AppDb, val profileId: Int) {
} }
fun saveData() { fun saveData() {
if (profile == null)
return
if (teacherList.isNotEmpty()) { if (teacherList.isNotEmpty()) {
val tempList: ArrayList<Teacher> = ArrayList() val tempList: ArrayList<Teacher> = ArrayList()
teacherList.forEach { _, teacher -> teacherList.forEach { _, teacher ->
tempList.add(teacher) tempList.add(teacher)
} }
appDb.teacherDao().addAll(tempList) db.teacherDao().addAll(tempList)
} }
if (subjectList.isNotEmpty()) { if (subjectList.isNotEmpty()) {
val tempList: ArrayList<Subject> = ArrayList() val tempList: ArrayList<Subject> = ArrayList()
subjectList.forEach { _, subject -> subjectList.forEach { _, subject ->
tempList.add(subject) tempList.add(subject)
} }
appDb.subjectDao().addAll(tempList) db.subjectDao().addAll(tempList)
} }
if (teamList.isNotEmpty()) if (teamList.isNotEmpty())
appDb.teamDao().addAll(teamList) db.teamDao().addAll(teamList)
if (lessonList.isNotEmpty()) { if (lessonList.isNotEmpty()) {
appDb.lessonDao().clear(profileId) db.lessonDao().clear(profile.id)
appDb.lessonDao().addAll(lessonList) db.lessonDao().addAll(lessonList)
} }
if (lessonChangeList.isNotEmpty()) if (lessonChangeList.isNotEmpty())
appDb.lessonChangeDao().addAll(lessonChangeList) db.lessonChangeDao().addAll(lessonChangeList)
if (gradeCategoryList.isNotEmpty()) if (gradeCategoryList.isNotEmpty())
appDb.gradeCategoryDao().addAll(gradeCategoryList) db.gradeCategoryDao().addAll(gradeCategoryList)
if (gradeList.isNotEmpty()) { if (gradeList.isNotEmpty()) {
appDb.gradeDao().clear(profileId) db.gradeDao().clear(profile.id)
appDb.gradeDao().addAll(gradeList) db.gradeDao().addAll(gradeList)
} }
if (eventList.isNotEmpty()) { if (eventList.isNotEmpty()) {
appDb.eventDao().removeFuture(profileId, Date.getToday()) db.eventDao().removeFuture(profile.id, Date.getToday())
appDb.eventDao().addAll(eventList) db.eventDao().addAll(eventList)
} }
if (eventTypeList.isNotEmpty()) if (eventTypeList.isNotEmpty())
appDb.eventTypeDao().addAll(eventTypeList) db.eventTypeDao().addAll(eventTypeList)
if (noticeList.isNotEmpty()) { if (noticeList.isNotEmpty()) {
appDb.noticeDao().clear(profileId) db.noticeDao().clear(profile.id)
appDb.noticeDao().addAll(noticeList) db.noticeDao().addAll(noticeList)
} }
if (attendanceList.isNotEmpty()) if (attendanceList.isNotEmpty())
appDb.attendanceDao().addAll(attendanceList) db.attendanceDao().addAll(attendanceList)
if (announcementList.isNotEmpty()) if (announcementList.isNotEmpty())
appDb.announcementDao().addAll(announcementList) db.announcementDao().addAll(announcementList)
if (messageList.isNotEmpty()) if (messageList.isNotEmpty())
appDb.messageDao().addAllIgnore(messageList) db.messageDao().addAllIgnore(messageList)
if (messageRecipientList.isNotEmpty()) if (messageRecipientList.isNotEmpty())
appDb.messageRecipientDao().addAll(messageRecipientList) db.messageRecipientDao().addAll(messageRecipientList)
if (messageRecipientIgnoreList.isNotEmpty()) if (messageRecipientIgnoreList.isNotEmpty())
appDb.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList) db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList)
if (metadataList.isNotEmpty()) if (metadataList.isNotEmpty())
appDb.metadataDao().addAllIgnore(metadataList) db.metadataDao().addAllIgnore(metadataList)
if (messageMetadataList.isNotEmpty()) if (messageMetadataList.isNotEmpty())
appDb.metadataDao().setSeen(messageMetadataList) db.metadataDao().setSeen(messageMetadataList)
} }
} }

View File

@ -14,13 +14,14 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
* @param loginType type of the e-register this endpoint handles * @param loginType type of the e-register this endpoint handles
* @param endpointId a unique ID of this endpoint * @param endpointId a unique ID of this endpoint
* @param featureIds a [List] of [Feature]s (their IDs) this endpoint can download * @param featureIds a [List] of [Feature]s (their IDs) this endpoint can download
* May be null if no strict feature set is associated with this method.
* @param endpointClass a [Class] which constructor will be invoked when a data download is needed * @param endpointClass a [Class] which constructor will be invoked when a data download is needed
* @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore]. * @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore].
*/ */
class Endpoint( class Endpoint(
val loginType: Int, val loginType: Int,
val endpointId: Int, val endpointId: Int,
val featureIds: List<Int>, val featureIds: List<Int>?,
val endpointClass: Class<*>, val endpointClass: Class<*>,
val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int
) )

View File

@ -1,6 +1,5 @@
package pl.szczodrzynski.edziennik.api.v2.models package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.api.v2.endpoint
data class Feature(val featureId: Int, val loginOptions: Map<Int, List<Int>>) { data class Feature(val featureId: Int, val loginOptions: Map<Int, List<Int>>) {

View File

@ -17,13 +17,14 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
* @param loginType type of the e-register this login method handles * @param loginType type of the e-register this login method handles
* @param loginMethodId a unique ID of this login method * @param loginMethodId a unique ID of this login method
* @param featureIds a [List] of [Feature]s (their IDs) this login method can provide access to * @param featureIds a [List] of [Feature]s (their IDs) this login method can provide access to
* May be null if no strict feature set is associated with this method.
* @param loginMethodClass a [Class] which constructor will be invoked when a log in is needed * @param loginMethodClass a [Class] which constructor will be invoked when a log in is needed
* @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore]. * @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore].
*/ */
class LoginMethod( class LoginMethod(
val loginType: Int, val loginType: Int,
val loginMethodId: Int, val loginMethodId: Int,
val featureIds: List<Int>, val featureIds: List<Int>?,
val loginMethodClass: Class<*>, val loginMethodClass: Class<*>,
val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int
) )

View File

@ -44,7 +44,8 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.api.AppError; import pl.szczodrzynski.edziennik.api.AppError;
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.api.v2.librus.Librus; import pl.szczodrzynski.edziennik.api.v2.librus.LibrusOld;
import pl.szczodrzynski.edziennik.api.v2.librus.LibrusTest;
import pl.szczodrzynski.edziennik.databinding.CardLuckyNumberBinding; import pl.szczodrzynski.edziennik.databinding.CardLuckyNumberBinding;
import pl.szczodrzynski.edziennik.databinding.CardUpdateBinding; import pl.szczodrzynski.edziennik.databinding.CardUpdateBinding;
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding; import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding;
@ -115,45 +116,18 @@ public class HomeFragment extends Fragment {
return true; return true;
});*/ });*/
b.testButton.setOnClickListener((v -> {
LoginStore loginStore = new LoginStore(1, LOGIN_TYPE_LIBRUS, new JsonObject());
loginStore.putLoginData("email", "example@example.com");
loginStore.putLoginData("password", "zaq1@WSX");
Profile profile = new Profile(1, "test", "testsubname", 1);
profile.putStudentData("accountLogin", "1234567");
new Librus(app, profile, loginStore).login(new SyncCallback() {
@Override
public void onLoginFirst(List<Profile> profileList, LoginStore loginStore) {
}
@Override
public void onSuccess(Context activityContext, ProfileFull profileFull) {
}
@Override
public void onProgress(int progressStep) {
}
@Override
public void onActionStarted(int stringResId) {
}
@Override
public void onError(Context activityContext, @NonNull AppError error) {
}
});
}));
b.composeButton.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE); b.composeButton.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.composeButton.setOnClickListener((v -> { b.composeButton.setOnClickListener((v -> {
startActivity(new Intent(activity, MessagesComposeActivity.class)); startActivity(new Intent(activity, MessagesComposeActivity.class));
})); }));
LibrusTest test = new LibrusTest(app);
b.testButton.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.testButton.setOnClickListener((v -> {
test.go();
}));
//((TextView)v.findViewById(R.id.nextSync)).setText(getString(R.string.next_sync_format,Time.fromMillis(app.appJobs.syncJobTime).getStringHMS())); //((TextView)v.findViewById(R.id.nextSync)).setText(getString(R.string.next_sync_format,Time.fromMillis(app.appJobs.syncJobTime).getStringHMS()));