Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit 4c94137

Browse files
authored
Refactor BiometricAuthenticator and add proper support for retries (#1627)
1 parent 8b5be3f commit 4c94137

File tree

6 files changed

+58
-16
lines changed

6 files changed

+58
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
2323
- .gpg-id file generated by APS did not work with pass CLI
2424
- All but the latest launcher shortcut would have an empty icon
2525
- When prompted to select a GPG key during onboarding, the app would crash if the user did not make a selection in OpenKeychain
26+
- Biometric authentication prompts no longer inexplicably dismiss when an incorrect biometric is entered
2627

2728
### Changed
2829

app/src/main/java/dev/msfjarvis/aps/ui/main/LaunchActivity.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import dev.msfjarvis.aps.ui.crypto.BasePgpActivity
1414
import dev.msfjarvis.aps.ui.crypto.DecryptActivity
1515
import dev.msfjarvis.aps.ui.passwords.PasswordStore
1616
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
17+
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result
1718
import dev.msfjarvis.aps.util.extensions.sharedPrefs
1819
import dev.msfjarvis.aps.util.settings.PreferenceKeys
1920

@@ -23,18 +24,19 @@ class LaunchActivity : AppCompatActivity() {
2324
super.onCreate(savedInstanceState)
2425
val prefs = sharedPrefs
2526
if (prefs.getBoolean(PreferenceKeys.BIOMETRIC_AUTH, false)) {
26-
BiometricAuthenticator.authenticate(this) {
27-
when (it) {
28-
is BiometricAuthenticator.Result.Success -> {
27+
BiometricAuthenticator.authenticate(this) { result ->
28+
when (result) {
29+
is Result.Success -> {
2930
startTargetActivity(false)
3031
}
31-
is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
32+
is Result.HardwareUnavailableOrDisabled -> {
3233
prefs.edit { remove(PreferenceKeys.BIOMETRIC_AUTH) }
3334
startTargetActivity(false)
3435
}
35-
is BiometricAuthenticator.Result.Failure, BiometricAuthenticator.Result.Cancelled -> {
36+
is Result.Failure, Result.Cancelled -> {
3637
finish()
3738
}
39+
is Result.Retry -> {}
3840
}
3941
}
4042
} else {

app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import de.Maxr1998.modernpreferences.helpers.singleChoice
1717
import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem
1818
import dev.msfjarvis.aps.R
1919
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
20+
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result
2021
import dev.msfjarvis.aps.util.extensions.sharedPrefs
2122
import dev.msfjarvis.aps.util.settings.PreferenceKeys
2223

@@ -73,11 +74,12 @@ class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider
7374
activity.sharedPrefs.edit {
7475
BiometricAuthenticator.authenticate(activity) { result ->
7576
when (result) {
76-
is BiometricAuthenticator.Result.Success -> {
77+
is Result.Success -> {
7778
// Apply the changes
7879
putBoolean(PreferenceKeys.BIOMETRIC_AUTH, checked)
7980
enabled = true
8081
}
82+
is Result.Retry -> {}
8183
else -> {
8284
// If any error occurs, revert back to the previous
8385
// state. This

app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import dev.msfjarvis.aps.R
2222
import dev.msfjarvis.aps.databinding.ActivitySshKeygenBinding
2323
import dev.msfjarvis.aps.injection.prefs.GitPreferences
2424
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
25+
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result
2526
import dev.msfjarvis.aps.util.extensions.keyguardManager
2627
import dev.msfjarvis.aps.util.extensions.viewBinding
2728
import dev.msfjarvis.aps.util.git.sshj.SshKey
@@ -121,17 +122,17 @@ class SshKeyGenActivity : AppCompatActivity() {
121122
if (requireAuthentication) {
122123
val result =
123124
withContext(Dispatchers.Main) {
124-
suspendCoroutine<BiometricAuthenticator.Result> { cont ->
125+
suspendCoroutine<Result> { cont ->
125126
BiometricAuthenticator.authenticate(
126127
this@SshKeyGenActivity,
127128
R.string.biometric_prompt_title_ssh_keygen
128-
) {
129+
) { result ->
129130
// Do not cancel on failed attempts as these are handled by the authenticator UI.
130-
if (it !is BiometricAuthenticator.Result.Failure) cont.resume(it)
131+
if (result !is Result.Retry) cont.resume(result)
131132
}
132133
}
133134
}
134-
if (result !is BiometricAuthenticator.Result.Success)
135+
if (result !is Result.Success)
135136
throw UserNotAuthenticatedException(getString(R.string.biometric_auth_generic_failure))
136137
}
137138
keyGenType.generateKey(requireAuthentication)

app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,28 @@ object BiometricAuthenticator {
2121
private const val validAuthenticators =
2222
Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK
2323

24+
/**
25+
* Sealed class to wrap [BiometricPrompt]'s [Int]-based return codes into more easily-interpreted
26+
* types.
27+
*/
2428
sealed class Result {
29+
30+
/** Biometric authentication was a success. */
2531
data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result()
32+
33+
/** Biometric authentication has irreversibly failed. */
2634
data class Failure(val code: Int?, val message: CharSequence) : Result()
35+
36+
/**
37+
* An incorrect biometric was entered, but the prompt UI is offering the option to retry the
38+
* operation.
39+
*/
40+
object Retry : Result()
41+
42+
/** The biometric hardware is unavailable or disabled on a software or hardware level. */
2743
object HardwareUnavailableOrDisabled : Result()
44+
45+
/** The prompt was dismissed. */
2846
object Cancelled : Result()
2947
}
3048

@@ -56,18 +74,35 @@ object BiometricAuthenticator {
5674
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> {
5775
Result.HardwareUnavailableOrDisabled
5876
}
59-
else ->
77+
BiometricPrompt.ERROR_LOCKOUT,
78+
BiometricPrompt.ERROR_LOCKOUT_PERMANENT,
79+
BiometricPrompt.ERROR_NO_SPACE,
80+
BiometricPrompt.ERROR_TIMEOUT,
81+
BiometricPrompt.ERROR_VENDOR -> {
82+
Result.Failure(
83+
errorCode,
84+
activity.getString(R.string.biometric_auth_error_reason, errString)
85+
)
86+
}
87+
BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> {
88+
Result.Retry
89+
}
90+
// We cover all guaranteed values above, but [errorCode] is still an Int at the end of
91+
// the day so a
92+
// catch-all else will always be required.
93+
else -> {
6094
Result.Failure(
6195
errorCode,
6296
activity.getString(R.string.biometric_auth_error_reason, errString)
6397
)
98+
}
6499
}
65100
)
66101
}
67102

68103
override fun onAuthenticationFailed() {
69104
super.onAuthenticationFailed()
70-
callback(Result.Failure(null, activity.getString(R.string.biometric_auth_error)))
105+
callback(Result.Retry)
71106
}
72107

73108
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {

app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import dev.msfjarvis.aps.data.repo.PasswordRepository
2222
import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity
2323
import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity
2424
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator
25+
import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result.*
2526
import dev.msfjarvis.aps.util.git.GitCommandExecutor
2627
import dev.msfjarvis.aps.util.git.sshj.ContinuationContainerActivity
2728
import dev.msfjarvis.aps.util.git.sshj.SshAuthMethod
@@ -172,17 +173,17 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
172173
BiometricAuthenticator.authenticate(
173174
callingActivity,
174175
R.string.biometric_prompt_title_ssh_auth
175-
) { if (it !is BiometricAuthenticator.Result.Failure) cont.resume(it) }
176+
) { result -> if (result !is Failure) cont.resume(result) }
176177
}
177178
}
178179
when (result) {
179-
is BiometricAuthenticator.Result.Success -> {
180+
is Success -> {
180181
registerAuthProviders(SshAuthMethod.SshKey(authActivity))
181182
}
182-
is BiometricAuthenticator.Result.Cancelled -> {
183+
is Cancelled -> {
183184
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
184185
}
185-
is BiometricAuthenticator.Result.Failure -> {
186+
is Failure -> {
186187
throw IllegalStateException("Biometric authentication failures should be ignored")
187188
}
188189
else -> {

0 commit comments

Comments
 (0)