forked from github/wulkanowy-mirror
Add clearing all data and key entry when decryption failed (#2386)
This commit is contained in:
parent
40df80371c
commit
6ee38e9259
@ -1,11 +1,16 @@
|
|||||||
package io.github.wulkanowy.data.db.dao
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentName
|
import io.github.wulkanowy.data.db.entities.StudentName
|
||||||
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
|
||||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@ -47,6 +52,9 @@ abstract class StudentDao {
|
|||||||
@Query("UPDATE Students SET is_current = 0")
|
@Query("UPDATE Students SET is_current = 0")
|
||||||
abstract suspend fun resetCurrent()
|
abstract suspend fun resetCurrent()
|
||||||
|
|
||||||
|
@Query("DELETE FROM Students WHERE email = :email AND user_name = :userName")
|
||||||
|
abstract suspend fun deleteByEmailAndUserName(email: String, userName: String)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open suspend fun switchCurrent(id: Long) {
|
open suspend fun switchCurrent(id: Long) {
|
||||||
resetCurrent()
|
resetCurrent()
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
@ -17,20 +15,19 @@ import io.github.wulkanowy.data.pojos.RegisterUser
|
|||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.security.decrypt
|
import io.github.wulkanowy.utils.security.Scrambler
|
||||||
import io.github.wulkanowy.utils.security.encrypt
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class StudentRepository @Inject constructor(
|
class StudentRepository @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val dispatchers: DispatchersProvider,
|
private val dispatchers: DispatchersProvider,
|
||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk,
|
||||||
private val appDatabase: AppDatabase
|
private val appDatabase: AppDatabase,
|
||||||
|
private val scrambler: Scrambler,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
|
suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
|
||||||
@ -68,7 +65,7 @@ class StudentRepository @Inject constructor(
|
|||||||
student = student.apply {
|
student = student.apply {
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
scrambler.decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -86,7 +83,7 @@ class StudentRepository @Inject constructor(
|
|||||||
}.apply {
|
}.apply {
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
scrambler.decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +93,7 @@ class StudentRepository @Inject constructor(
|
|||||||
|
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
scrambler.decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return student
|
return student
|
||||||
@ -107,7 +104,7 @@ class StudentRepository @Inject constructor(
|
|||||||
|
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.HEBE) {
|
||||||
student.password = withContext(dispatchers.io) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
scrambler.decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return student
|
return student
|
||||||
@ -120,7 +117,7 @@ class StudentRepository @Inject constructor(
|
|||||||
it.apply {
|
it.apply {
|
||||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) {
|
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.HEBE) {
|
||||||
password = withContext(dispatchers.io) {
|
password = withContext(dispatchers.io) {
|
||||||
encrypt(password, context)
|
scrambler.encrypt(password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,4 +163,15 @@ class StudentRepository @Inject constructor(
|
|||||||
|
|
||||||
studentDb.update(studentName)
|
studentDb.update(studentName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteStudentsAssociatedWithAccount(student: Student) {
|
||||||
|
studentDb.deleteByEmailAndUserName(student.email, student.userName)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clearAll() {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
scrambler.clearKeyPair()
|
||||||
|
appDatabase.clearAllTables()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,11 +68,20 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
|||||||
} else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
|
} else Toast.makeText(this, text, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.main_expired_credentials_title)
|
||||||
|
.setMessage(R.string.main_expired_credentials_description)
|
||||||
|
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmExpiredCredentialsSelected() }
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.main_session_expired)
|
.setTitle(R.string.main_session_expired)
|
||||||
.setMessage(R.string.main_session_relogin)
|
.setMessage(R.string.main_session_relogin)
|
||||||
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() }
|
.setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onConfirmDecryptionFailedSelected() }
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,12 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
|
|||||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openClearLoginView() {
|
override fun openClearLoginView() {
|
||||||
|
@ -39,8 +39,12 @@ abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showAuthDialog() {
|
override fun showAuthDialog() {
|
||||||
|
@ -28,20 +28,37 @@ open class BasePresenter<T : BaseView>(
|
|||||||
this.view = view
|
this.view = view
|
||||||
errorHandler.apply {
|
errorHandler.apply {
|
||||||
showErrorMessage = view::showError
|
showErrorMessage = view::showError
|
||||||
onSessionExpired = view::showExpiredDialog
|
onExpiredCredentials = view::showExpiredCredentialsDialog
|
||||||
|
onDecryptionFailed = view::showDecryptionFailedDialog
|
||||||
onNoCurrentStudent = view::openClearLoginView
|
onNoCurrentStudent = view::openClearLoginView
|
||||||
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
onPasswordChangeRequired = view::showChangePasswordSnackbar
|
||||||
onAuthorizationRequired = view::showAuthDialog
|
onAuthorizationRequired = view::showAuthDialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onExpiredLoginSelected() {
|
fun onConfirmDecryptionFailedSelected() {
|
||||||
Timber.i("Attempt to switch the student after the session expires")
|
Timber.i("Attempt to clear all data")
|
||||||
|
|
||||||
|
presenterScope.launch {
|
||||||
|
runCatching { studentRepository.clearAll() }
|
||||||
|
.onFailure {
|
||||||
|
Timber.i("Clear data result: An exception occurred")
|
||||||
|
errorHandler.dispatch(it)
|
||||||
|
}
|
||||||
|
.onSuccess {
|
||||||
|
Timber.i("Clear data result: Open login view")
|
||||||
|
view?.openClearLoginView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onConfirmExpiredCredentialsSelected() {
|
||||||
|
Timber.i("Attempt to delete students associated with the account and switch to new student")
|
||||||
|
|
||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
val student = studentRepository.getCurrentStudent(false)
|
val student = studentRepository.getCurrentStudent(false)
|
||||||
studentRepository.logoutStudent(student)
|
studentRepository.deleteStudentsAssociatedWithAccount(student)
|
||||||
|
|
||||||
val students = studentRepository.getSavedStudents(false)
|
val students = studentRepository.getSavedStudents(false)
|
||||||
if (students.isNotEmpty()) {
|
if (students.isNotEmpty()) {
|
||||||
@ -50,11 +67,11 @@ open class BasePresenter<T : BaseView>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
Timber.i("Switch student result: An exception occurred")
|
Timber.i("Delete students result: An exception occurred")
|
||||||
errorHandler.dispatch(it)
|
errorHandler.dispatch(it)
|
||||||
}
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
Timber.i("Switch student result: Open login view")
|
Timber.i("Delete students result: Open login view")
|
||||||
view?.openClearLoginView()
|
view?.openClearLoginView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@ interface BaseView {
|
|||||||
|
|
||||||
fun showMessage(text: String)
|
fun showMessage(text: String)
|
||||||
|
|
||||||
fun showExpiredDialog()
|
fun showExpiredCredentialsDialog()
|
||||||
|
|
||||||
|
fun showDecryptionFailedDialog()
|
||||||
|
|
||||||
fun showAuthDialog()
|
fun showAuthDialog()
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
|||||||
|
|
||||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
var onSessionExpired: () -> Unit = {}
|
var onExpiredCredentials: () -> Unit = {}
|
||||||
|
|
||||||
|
var onDecryptionFailed: () -> Unit = {}
|
||||||
|
|
||||||
var onNoCurrentStudent: () -> Unit = {}
|
var onNoCurrentStudent: () -> Unit = {}
|
||||||
|
|
||||||
@ -32,7 +34,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
|||||||
showErrorMessage(context.resources.getErrorString(error), error)
|
showErrorMessage(context.resources.getErrorString(error), error)
|
||||||
when (error) {
|
when (error) {
|
||||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
is ScramblerException -> onDecryptionFailed()
|
||||||
|
is BadCredentialsException -> onExpiredCredentials()
|
||||||
is NoCurrentStudentException -> onNoCurrentStudent()
|
is NoCurrentStudentException -> onNoCurrentStudent()
|
||||||
is AuthorizationRequiredException -> onAuthorizationRequired()
|
is AuthorizationRequiredException -> onAuthorizationRequired()
|
||||||
}
|
}
|
||||||
@ -40,7 +43,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co
|
|||||||
|
|
||||||
open fun clear() {
|
open fun clear() {
|
||||||
showErrorMessage = { _, _ -> }
|
showErrorMessage = { _, _ -> }
|
||||||
onSessionExpired = {}
|
onExpiredCredentials = {}
|
||||||
|
onDecryptionFailed = {}
|
||||||
onNoCurrentStudent = {}
|
onNoCurrentStudent = {}
|
||||||
onPasswordChangeRequired = {}
|
onPasswordChangeRequired = {}
|
||||||
onAuthorizationRequired = {}
|
onAuthorizationRequired = {}
|
||||||
|
@ -30,7 +30,12 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
|
|||||||
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
import io.github.wulkanowy.ui.modules.notificationscenter.NotificationsCenterFragment
|
||||||
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
import io.github.wulkanowy.utils.*
|
import io.github.wulkanowy.utils.capitalise
|
||||||
|
import io.github.wulkanowy.utils.dpToPx
|
||||||
|
import io.github.wulkanowy.utils.getErrorString
|
||||||
|
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||||
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -24,7 +24,9 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin
|
|||||||
|
|
||||||
override fun showMessage(text: String) {}
|
override fun showMessage(text: String) {}
|
||||||
|
|
||||||
override fun showExpiredDialog() {}
|
override fun showExpiredCredentialsDialog() {}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {}
|
||||||
|
|
||||||
override fun openClearLoginView() {}
|
override fun openClearLoginView() {}
|
||||||
|
|
||||||
|
@ -47,8 +47,12 @@ class AdvancedFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||||
|
@ -63,8 +63,12 @@ class AppearanceFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||||
|
@ -133,8 +133,12 @@ class NotificationsFragment : PreferenceFragmentCompat(),
|
|||||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||||
|
@ -84,8 +84,12 @@ class SyncFragment : PreferenceFragmentCompat(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||||
|
@ -16,6 +16,7 @@ import android.util.Base64.DEFAULT
|
|||||||
import android.util.Base64.decode
|
import android.util.Base64.decode
|
||||||
import android.util.Base64.encode
|
import android.util.Base64.encode
|
||||||
import android.util.Base64.encodeToString
|
import android.util.Base64.encodeToString
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@ -33,13 +34,15 @@ import javax.crypto.CipherInputStream
|
|||||||
import javax.crypto.CipherOutputStream
|
import javax.crypto.CipherOutputStream
|
||||||
import javax.crypto.spec.OAEPParameterSpec
|
import javax.crypto.spec.OAEPParameterSpec
|
||||||
import javax.crypto.spec.PSource.PSpecified
|
import javax.crypto.spec.PSource.PSpecified
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
private const val KEYSTORE_NAME = "AndroidKeyStore"
|
@Singleton
|
||||||
|
class Scrambler @Inject constructor(
|
||||||
private const val KEY_ALIAS = "wulkanowy_password"
|
@ApplicationContext private val context: Context,
|
||||||
|
) {
|
||||||
private val KEY_CHARSET = Charset.forName("UTF-8")
|
private val keyCharset = Charset.forName("UTF-8")
|
||||||
|
|
||||||
private val isKeyPairExists: Boolean
|
private val isKeyPairExists: Boolean
|
||||||
get() = keyStore.getKey(KEY_ALIAS, null) != null
|
get() = keyStore.getKey(KEY_ALIAS, null) != null
|
||||||
@ -56,11 +59,11 @@ private val cipher: Cipher
|
|||||||
else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL")
|
else Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encrypt(plainText: String, context: Context): String {
|
fun encrypt(plainText: String): String {
|
||||||
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
if (!isKeyPairExists) generateKeyPair(context)
|
if (!isKeyPairExists) generateKeyPair()
|
||||||
|
|
||||||
cipher.let {
|
cipher.let {
|
||||||
if (SDK_INT >= M) {
|
if (SDK_INT >= M) {
|
||||||
@ -71,7 +74,7 @@ fun encrypt(plainText: String, context: Context): String {
|
|||||||
|
|
||||||
ByteArrayOutputStream().let { output ->
|
ByteArrayOutputStream().let { output ->
|
||||||
CipherOutputStream(output, it).apply {
|
CipherOutputStream(output, it).apply {
|
||||||
write(plainText.toByteArray(KEY_CHARSET))
|
write(plainText.toByteArray(keyCharset))
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
encodeToString(output.toByteArray(), DEFAULT)
|
encodeToString(output.toByteArray(), DEFAULT)
|
||||||
@ -79,7 +82,7 @@ fun encrypt(plainText: String, context: Context): String {
|
|||||||
}
|
}
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Timber.e(exception, "An error occurred while encrypting text")
|
Timber.e(exception, "An error occurred while encrypting text")
|
||||||
String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
|
String(encode(plainText.toByteArray(keyCharset), DEFAULT), keyCharset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +99,10 @@ fun decrypt(cipherText: String): String {
|
|||||||
}
|
}
|
||||||
} else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null))
|
} else it.init(DECRYPT_MODE, keyStore.getKey(KEY_ALIAS, null))
|
||||||
|
|
||||||
CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input ->
|
CipherInputStream(
|
||||||
|
ByteArrayInputStream(decode(cipherText, DEFAULT)),
|
||||||
|
it
|
||||||
|
).let { input ->
|
||||||
val values = ArrayList<Byte>()
|
val values = ArrayList<Byte>()
|
||||||
var nextByte: Int
|
var nextByte: Int
|
||||||
while (run { nextByte = input.read(); nextByte } != -1) {
|
while (run { nextByte = input.read(); nextByte } != -1) {
|
||||||
@ -106,7 +112,7 @@ fun decrypt(cipherText: String): String {
|
|||||||
for (i in bytes.indices) {
|
for (i in bytes.indices) {
|
||||||
bytes[i] = values[i]
|
bytes[i] = values[i]
|
||||||
}
|
}
|
||||||
String(bytes, 0, bytes.size, KEY_CHARSET)
|
String(bytes, 0, bytes.size, keyCharset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -114,7 +120,7 @@ fun decrypt(cipherText: String): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateKeyPair(context: Context) {
|
private fun generateKeyPair() {
|
||||||
(if (SDK_INT >= M) {
|
(if (SDK_INT >= M) {
|
||||||
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
|
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
|
||||||
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
|
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
|
||||||
@ -138,3 +144,14 @@ private fun generateKeyPair(context: Context) {
|
|||||||
}
|
}
|
||||||
Timber.i("A new KeyPair has been generated")
|
Timber.i("A new KeyPair has been generated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearKeyPair() {
|
||||||
|
keyStore.deleteEntry(KEY_ALIAS)
|
||||||
|
Timber.i("KeyPair has been cleared")
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val KEYSTORE_NAME = "AndroidKeyStore"
|
||||||
|
private const val KEY_ALIAS = "wulkanowy_password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -107,6 +107,8 @@
|
|||||||
<string name="main_log_in">Log in</string>
|
<string name="main_log_in">Log in</string>
|
||||||
<string name="main_session_expired">Session expired</string>
|
<string name="main_session_expired">Session expired</string>
|
||||||
<string name="main_session_relogin">Session expired, log in again</string>
|
<string name="main_session_relogin">Session expired, log in again</string>
|
||||||
|
<string name="main_expired_credentials_description">Your account password has been changed. You need to log in to Wulkanowy again</string>
|
||||||
|
<string name="main_expired_credentials_title">Password changed</string>
|
||||||
<string name="main_support_title">Application support</string>
|
<string name="main_support_title">Application support</string>
|
||||||
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
<string name="main_support_description">Do you like this app? Support its development by enabling non-invasive ads that you can disable at any time</string>
|
||||||
<string name="main_support_positive">Enable ads</string>
|
<string name="main_support_positive">Enable ads</string>
|
||||||
|
@ -101,8 +101,12 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
|
|||||||
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
(activity as? BaseActivity<*, *>)?.showMessage(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExpiredDialog() {
|
override fun showExpiredCredentialsDialog() {
|
||||||
(activity as? BaseActivity<*, *>)?.showExpiredDialog()
|
(activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showDecryptionFailedDialog() {
|
||||||
|
(activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
override fun showChangePasswordSnackbar(redirectUrl: String) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user