From b090468109eda390bfbd3eb4d713c9a0698844a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 20 Aug 2020 14:12:40 +0200 Subject: [PATCH] Logout after 3 wrong PIN codes --- .../vector/app/features/pin/PinCodeStore.kt | 57 +++++++++++++++++++ .../im/vector/app/features/pin/PinFragment.kt | 38 ++++++++++++- vector/src/main/res/values/strings.xml | 6 ++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt b/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt index 348fa4da27..630b165214 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt @@ -37,6 +37,25 @@ interface PinCodeStore { fun getEncodedPin(): String? suspend fun hasEncodedPin(): Boolean + + fun getRemainingPinCodeAttemptsNumber(): Int + + fun getRemainingBiometricsAttemptsNumber(): Int + + /** + * Will return the number of remaining attempts + */ + fun onWrongPin(): Int + + /** + * Will return the number of remaining attempts + */ + fun onWrongBiometrics(): Int + + /** + * Will reset the counters + */ + fun resetCounters() } class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore { @@ -48,6 +67,8 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: } override suspend fun deleteEncodedPin() = withContext(Dispatchers.IO) { + // Also reset the counters + resetCounters() sharedPreferences.edit { remove(ENCODED_PIN_CODE_KEY) } @@ -72,11 +93,47 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: result.error == null && result.result } + override fun getRemainingPinCodeAttemptsNumber(): Int { + return sharedPreferences.getInt(REMAINING_PIN_CODE_ATTEMPTS_KEY, MAX_PIN_CODE_ATTEMPTS_NUMBER_BEFORE_LOGOUT) + } + + override fun getRemainingBiometricsAttemptsNumber(): Int { + return sharedPreferences.getInt(REMAINING_BIOMETRICS_ATTEMPTS_KEY, MAX_BIOMETRIC_ATTEMPTS_NUMBER_BEFORE_FORCE_PIN) + } + + override fun onWrongPin(): Int { + val remaining = getRemainingPinCodeAttemptsNumber() - 1 + sharedPreferences.edit { + putInt(REMAINING_PIN_CODE_ATTEMPTS_KEY, remaining) + } + return remaining + } + + override fun onWrongBiometrics(): Int { + val remaining = getRemainingBiometricsAttemptsNumber() - 1 + sharedPreferences.edit { + putInt(REMAINING_BIOMETRICS_ATTEMPTS_KEY, remaining) + } + return remaining + } + + override fun resetCounters() { + sharedPreferences.edit { + remove(REMAINING_PIN_CODE_ATTEMPTS_KEY) + remove(REMAINING_BIOMETRICS_ATTEMPTS_KEY) + } + } + private suspend inline fun awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback) -> Unit) = suspendCoroutine> { cont -> callback(PFPinCodeHelperCallback { result -> cont.resume(result) }) } companion object { private const val ENCODED_PIN_CODE_KEY = "ENCODED_PIN_CODE_KEY" + private const val REMAINING_PIN_CODE_ATTEMPTS_KEY = "REMAINING_PIN_CODE_ATTEMPTS_KEY" + private const val REMAINING_BIOMETRICS_ATTEMPTS_KEY = "REMAINING_BIOMETRICS_ATTEMPTS_KEY" + + private const val MAX_PIN_CODE_ATTEMPTS_NUMBER_BEFORE_LOGOUT = 3 + private const val MAX_BIOMETRIC_ATTEMPTS_NUMBER_BEFORE_FORCE_PIN = 5 } } diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index 8ea385fa32..fedc705176 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -29,6 +29,7 @@ import com.beautycoder.pflockscreen.fragments.PFLockScreenFragment import im.vector.app.R import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.toast import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import kotlinx.android.parcel.Parcelize @@ -61,7 +62,7 @@ class PinFragment @Inject constructor( val encodedPin = pinCodeStore.getEncodedPin() ?: return val authFragment = PFLockScreenFragment() val builder = PFFLockScreenConfiguration.Builder(requireContext()) - .setUseBiometric(true) + .setUseBiometric(pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0) .setTitle(getString(R.string.auth_pin_confirm_to_disable_title)) .setClearCodeOnError(true) .setMode(PFFLockScreenConfiguration.MODE_AUTH) @@ -69,6 +70,7 @@ class PinFragment @Inject constructor( authFragment.setEncodedPinCode(encodedPin) authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener { override fun onPinLoginFailed() { + onWrongPin() } override fun onBiometricAuthSuccessful() { @@ -80,6 +82,12 @@ class PinFragment @Inject constructor( } override fun onBiometricAuthLoginFailed() { + val remainingAttempts = pinCodeStore.onWrongBiometrics() + if (remainingAttempts <= 0) { + // Disable Biometrics + builder.setUseBiometric(false) + authFragment.setConfiguration(builder.build()) + } } override fun onCodeInputSuccessful() { @@ -121,8 +129,12 @@ class PinFragment @Inject constructor( private fun showAuthFragment() { val encodedPin = pinCodeStore.getEncodedPin() ?: return val authFragment = PFLockScreenFragment() + val canUseBiometrics = pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0 val builder = PFFLockScreenConfiguration.Builder(requireContext()) .setUseBiometric(true) + .setAutoShowBiometric(true) + .setUseBiometric(canUseBiometrics) + .setAutoShowBiometric(canUseBiometrics) .setTitle(getString(R.string.auth_pin_title)) .setLeftButton(getString(R.string.auth_pin_forgot)) .setClearCodeOnError(true) @@ -134,17 +146,26 @@ class PinFragment @Inject constructor( } authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener { override fun onPinLoginFailed() { + onWrongPin() } override fun onBiometricAuthSuccessful() { + pinCodeStore.resetCounters() vectorBaseActivity.setResult(Activity.RESULT_OK) vectorBaseActivity.finish() } override fun onBiometricAuthLoginFailed() { + val remainingAttempts = pinCodeStore.onWrongBiometrics() + if (remainingAttempts <= 0) { + // Disable Biometrics + builder.setUseBiometric(false) + authFragment.setConfiguration(builder.build()) + } } override fun onCodeInputSuccessful() { + pinCodeStore.resetCounters() vectorBaseActivity.setResult(Activity.RESULT_OK) vectorBaseActivity.finish() } @@ -152,6 +173,21 @@ class PinFragment @Inject constructor( replaceFragment(R.id.pinFragmentContainer, authFragment) } + private fun onWrongPin() { + val remainingAttempts = pinCodeStore.onWrongPin() + when { + remainingAttempts > 1 -> + requireActivity().toast(resources.getQuantityString(R.plurals.wrong_pin_message_remaining_attempts, remainingAttempts, remainingAttempts)) + remainingAttempts == 1 -> + requireActivity().toast(R.string.wrong_pin_message_last_remaining_attempt) + else -> { + requireActivity().toast(R.string.too_many_pin_failures) + // Logout + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCredentials = true)) + } + } + } + private fun displayForgotPinWarningDialog() { AlertDialog.Builder(requireContext()) .setTitle(getString(R.string.auth_pin_reset_title)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 34cf24b476..f6ea5bf93b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2516,6 +2516,12 @@ Push notifications are disabled Review your settings to enable push notifications + + Wrong code, %d remaining attempt + Wrong code, %d remaining attempts + + Warning! Last remaining attempt before logout! + Too many errors, you\'ve been logged out Choose a PIN for security Confirm PIN Failed to validate pin, please tap a new one.