Logout after 3 wrong PIN codes

This commit is contained in:
Benoit Marty 2020-08-20 14:12:40 +02:00
parent 4cba1388f9
commit b090468109
3 changed files with 100 additions and 1 deletions

View File

@ -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 <T> awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback<T>) -> Unit) = suspendCoroutine<PFResult<T>> { cont ->
callback(PFPinCodeHelperCallback<T> { 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
}
}

View File

@ -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))

View File

@ -2516,6 +2516,12 @@
<string name="alert_push_are_disabled_title">Push notifications are disabled</string>
<string name="alert_push_are_disabled_description">Review your settings to enable push notifications</string>
<plurals name="wrong_pin_message_remaining_attempts">
<item quantity="one">Wrong code, %d remaining attempt</item>
<item quantity="other">Wrong code, %d remaining attempts</item>
</plurals>
<string name="wrong_pin_message_last_remaining_attempt">Warning! Last remaining attempt before logout!</string>
<string name="too_many_pin_failures">Too many errors, you\'ve been logged out</string>
<string name="create_pin_title">Choose a PIN for security</string>
<string name="create_pin_confirm_title">Confirm PIN</string>
<string name="create_pin_confirm_failure">Failed to validate pin, please tap a new one.</string>