Merge pull request #1968 from vector-im/feature/biometrics

Improve PIN code feature
This commit is contained in:
Benoit Marty 2020-08-20 17:16:27 +02:00 committed by GitHub
commit 187edbd32a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 8 deletions

View file

@ -42,7 +42,7 @@ allprojects {
// PhotoView
includeGroupByRegex 'com\\.github\\.chrisbanes'
// PFLockScreen-Android
includeGroupByRegex 'com\\.github\\.ganfra'
includeGroupByRegex 'com\\.github\\.vector-im'
}
}
maven {

View file

@ -346,7 +346,7 @@ dependencies {
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version"
implementation 'com.github.ganfra:PFLockScreen-Android:1.0.0-beta8'
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta9'
// Custom Tab
implementation 'androidx.browser:browser:1.2.0'

View file

@ -396,6 +396,11 @@ SOFTWARE.
<br/>
Copyright @ 2017-2018 Atlassian Pty Ltd
</li>
<li>
<b>PFLockScreen-Android</b>
<br/>
Copyright 2018, Aleksandr Nikiforov
</li>
</ul>
<pre>
Apache License

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())
.setUseFingerprint(true)
.setUseBiometric(pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0)
.setTitle(getString(R.string.auth_pin_confirm_to_disable_title))
.setClearCodeOnError(true)
.setMode(PFFLockScreenConfiguration.MODE_AUTH)
@ -69,9 +70,10 @@ class PinFragment @Inject constructor(
authFragment.setEncodedPinCode(encodedPin)
authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener {
override fun onPinLoginFailed() {
onWrongPin()
}
override fun onFingerprintSuccessful() {
override fun onBiometricAuthSuccessful() {
lifecycleScope.launch {
pinCodeStore.deleteEncodedPin()
vectorBaseActivity.setResult(Activity.RESULT_OK)
@ -79,7 +81,13 @@ class PinFragment @Inject constructor(
}
}
override fun onFingerprintLoginFailed() {
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())
.setUseFingerprint(true)
.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 onFingerprintSuccessful() {
override fun onBiometricAuthSuccessful() {
pinCodeStore.resetCounters()
vectorBaseActivity.setResult(Activity.RESULT_OK)
vectorBaseActivity.finish()
}
override fun onFingerprintLoginFailed() {
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

@ -2517,6 +2517,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>