diff options
6 files changed, 58 insertions, 16 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 307f7a82..2c7d2018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file. - .gpg-id file generated by APS did not work with pass CLI - All but the latest launcher shortcut would have an empty icon - When prompted to select a GPG key during onboarding, the app would crash if the user did not make a selection in OpenKeychain +- Biometric authentication prompts no longer inexplicably dismiss when an incorrect biometric is entered ### Changed diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/main/LaunchActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/main/LaunchActivity.kt index 629ddb9c..b5f7a593 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/main/LaunchActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/main/LaunchActivity.kt @@ -14,6 +14,7 @@ import dev.msfjarvis.aps.ui.crypto.BasePgpActivity import dev.msfjarvis.aps.ui.crypto.DecryptActivity import dev.msfjarvis.aps.ui.passwords.PasswordStore import dev.msfjarvis.aps.util.auth.BiometricAuthenticator +import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.settings.PreferenceKeys @@ -23,18 +24,19 @@ class LaunchActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val prefs = sharedPrefs if (prefs.getBoolean(PreferenceKeys.BIOMETRIC_AUTH, false)) { - BiometricAuthenticator.authenticate(this) { - when (it) { - is BiometricAuthenticator.Result.Success -> { + BiometricAuthenticator.authenticate(this) { result -> + when (result) { + is Result.Success -> { startTargetActivity(false) } - is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { + is Result.HardwareUnavailableOrDisabled -> { prefs.edit { remove(PreferenceKeys.BIOMETRIC_AUTH) } startTargetActivity(false) } - is BiometricAuthenticator.Result.Failure, BiometricAuthenticator.Result.Cancelled -> { + is Result.Failure, Result.Cancelled -> { finish() } + is Result.Retry -> {} } } } else { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt index 1e67a2b5..f78f2cb2 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/GeneralSettings.kt @@ -17,6 +17,7 @@ import de.Maxr1998.modernpreferences.helpers.singleChoice import de.Maxr1998.modernpreferences.preferences.choice.SelectionItem import dev.msfjarvis.aps.R import dev.msfjarvis.aps.util.auth.BiometricAuthenticator +import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.settings.PreferenceKeys @@ -73,11 +74,12 @@ class GeneralSettings(private val activity: FragmentActivity) : SettingsProvider activity.sharedPrefs.edit { BiometricAuthenticator.authenticate(activity) { result -> when (result) { - is BiometricAuthenticator.Result.Success -> { + is Result.Success -> { // Apply the changes putBoolean(PreferenceKeys.BIOMETRIC_AUTH, checked) enabled = true } + is Result.Retry -> {} else -> { // If any error occurs, revert back to the previous // state. This diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt index 0d8ffcbc..63abdf7c 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyGenActivity.kt @@ -22,6 +22,7 @@ import dev.msfjarvis.aps.R import dev.msfjarvis.aps.databinding.ActivitySshKeygenBinding import dev.msfjarvis.aps.injection.prefs.GitPreferences import dev.msfjarvis.aps.util.auth.BiometricAuthenticator +import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result import dev.msfjarvis.aps.util.extensions.keyguardManager import dev.msfjarvis.aps.util.extensions.viewBinding import dev.msfjarvis.aps.util.git.sshj.SshKey @@ -121,17 +122,17 @@ class SshKeyGenActivity : AppCompatActivity() { if (requireAuthentication) { val result = withContext(Dispatchers.Main) { - suspendCoroutine<BiometricAuthenticator.Result> { cont -> + suspendCoroutine<Result> { cont -> BiometricAuthenticator.authenticate( this@SshKeyGenActivity, R.string.biometric_prompt_title_ssh_keygen - ) { + ) { result -> // Do not cancel on failed attempts as these are handled by the authenticator UI. - if (it !is BiometricAuthenticator.Result.Failure) cont.resume(it) + if (result !is Result.Retry) cont.resume(result) } } } - if (result !is BiometricAuthenticator.Result.Success) + if (result !is Result.Success) throw UserNotAuthenticatedException(getString(R.string.biometric_auth_generic_failure)) } keyGenType.generateKey(requireAuthentication) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt b/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt index a582337b..50f11b5b 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/auth/BiometricAuthenticator.kt @@ -21,10 +21,28 @@ object BiometricAuthenticator { private const val validAuthenticators = Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK + /** + * Sealed class to wrap [BiometricPrompt]'s [Int]-based return codes into more easily-interpreted + * types. + */ sealed class Result { + + /** Biometric authentication was a success. */ data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result() + + /** Biometric authentication has irreversibly failed. */ data class Failure(val code: Int?, val message: CharSequence) : Result() + + /** + * An incorrect biometric was entered, but the prompt UI is offering the option to retry the + * operation. + */ + object Retry : Result() + + /** The biometric hardware is unavailable or disabled on a software or hardware level. */ object HardwareUnavailableOrDisabled : Result() + + /** The prompt was dismissed. */ object Cancelled : Result() } @@ -56,18 +74,35 @@ object BiometricAuthenticator { BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> { Result.HardwareUnavailableOrDisabled } - else -> + BiometricPrompt.ERROR_LOCKOUT, + BiometricPrompt.ERROR_LOCKOUT_PERMANENT, + BiometricPrompt.ERROR_NO_SPACE, + BiometricPrompt.ERROR_TIMEOUT, + BiometricPrompt.ERROR_VENDOR -> { + Result.Failure( + errorCode, + activity.getString(R.string.biometric_auth_error_reason, errString) + ) + } + BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> { + Result.Retry + } + // We cover all guaranteed values above, but [errorCode] is still an Int at the end of + // the day so a + // catch-all else will always be required. + else -> { Result.Failure( errorCode, activity.getString(R.string.biometric_auth_error_reason, errString) ) + } } ) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() - callback(Result.Failure(null, activity.getString(R.string.biometric_auth_error))) + callback(Result.Retry) } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt index 736b69c2..141de348 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt @@ -22,6 +22,7 @@ import dev.msfjarvis.aps.data.repo.PasswordRepository import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity import dev.msfjarvis.aps.util.auth.BiometricAuthenticator +import dev.msfjarvis.aps.util.auth.BiometricAuthenticator.Result.* import dev.msfjarvis.aps.util.git.GitCommandExecutor import dev.msfjarvis.aps.util.git.sshj.ContinuationContainerActivity import dev.msfjarvis.aps.util.git.sshj.SshAuthMethod @@ -172,17 +173,17 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { BiometricAuthenticator.authenticate( callingActivity, R.string.biometric_prompt_title_ssh_auth - ) { if (it !is BiometricAuthenticator.Result.Failure) cont.resume(it) } + ) { result -> if (result !is Failure) cont.resume(result) } } } when (result) { - is BiometricAuthenticator.Result.Success -> { + is Success -> { registerAuthProviders(SshAuthMethod.SshKey(authActivity)) } - is BiometricAuthenticator.Result.Cancelled -> { + is Cancelled -> { return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER)) } - is BiometricAuthenticator.Result.Failure -> { + is Failure -> { throw IllegalStateException("Biometric authentication failures should be ignored") } else -> { |