From 73695e2493af46c2375bb7678cb879d33a8aed4f Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 24 Apr 2020 15:00:33 +0530 Subject: auth: redo implementation with a cleaner and simpler API surface (#741) --- .../main/java/com/zeapo/pwdstore/LaunchActivity.kt | 18 +++--- .../main/java/com/zeapo/pwdstore/UserPreference.kt | 9 ++- .../zeapo/pwdstore/utils/BiometricAuthenticator.kt | 74 ++++++++++++++++++++++ .../pwdstore/utils/auth/AuthenticationResult.kt | 21 ------ .../com/zeapo/pwdstore/utils/auth/Authenticator.kt | 68 -------------------- app/src/main/res/values-ru/strings.xml | 3 - app/src/main/res/values/strings.xml | 5 +- 7 files changed, 90 insertions(+), 108 deletions(-) create mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt diff --git a/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt b/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt index 7db8cd48..b5902199 100644 --- a/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/LaunchActivity.kt @@ -8,10 +8,10 @@ import android.content.Intent import android.os.Bundle import android.os.Handler import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.zeapo.pwdstore.crypto.PgpActivity -import com.zeapo.pwdstore.utils.auth.AuthenticationResult -import com.zeapo.pwdstore.utils.auth.Authenticator +import com.zeapo.pwdstore.utils.BiometricAuthenticator class LaunchActivity : AppCompatActivity() { @@ -19,18 +19,20 @@ class LaunchActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val prefs = PreferenceManager.getDefaultSharedPreferences(this) if (prefs.getBoolean("biometric_auth", false)) { - Authenticator(this) { + BiometricAuthenticator.authenticate(this) { when (it) { - is AuthenticationResult.Success -> { + is BiometricAuthenticator.Result.Success -> { startTargetActivity(false) } - is AuthenticationResult.UnrecoverableError -> { - finish() + is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> { + prefs.edit { remove("biometric_auth") } + startTargetActivity(false) } - else -> { + is BiometricAuthenticator.Result.Failure, BiometricAuthenticator.Result.Cancelled -> { + finish() } } - }.authenticate() + } } else { startTargetActivity(true) } diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 32d1e87a..a9629c6c 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -46,9 +46,8 @@ import com.zeapo.pwdstore.git.GitServerConfigActivity import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity +import com.zeapo.pwdstore.utils.BiometricAuthenticator import com.zeapo.pwdstore.utils.PasswordRepository -import com.zeapo.pwdstore.utils.auth.AuthenticationResult -import com.zeapo.pwdstore.utils.auth.Authenticator import com.zeapo.pwdstore.utils.autofillManager import com.zeapo.pwdstore.utils.getEncryptedPrefs import java.io.File @@ -297,9 +296,9 @@ class UserPreference : AppCompatActivity() { isEnabled = false sharedPreferences.edit { val checked = isChecked - Authenticator(requireActivity()) { result -> + BiometricAuthenticator.authenticate(requireActivity()) { result -> when (result) { - is AuthenticationResult.Success -> { + is BiometricAuthenticator.Result.Success -> { // Apply the changes putBoolean("biometric_auth", checked) isEnabled = true @@ -312,7 +311,7 @@ class UserPreference : AppCompatActivity() { isEnabled = true } } - }.authenticate() + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { requireContext().getSystemService()?.apply { removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList()) diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt new file mode 100644 index 00000000..36d1f8f4 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt @@ -0,0 +1,74 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.utils + +import android.app.KeyguardManager +import android.os.Handler +import androidx.annotation.StringRes +import androidx.biometric.BiometricConstants +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.core.content.getSystemService +import androidx.fragment.app.FragmentActivity +import com.github.ajalt.timberkt.Timber.tag +import com.github.ajalt.timberkt.d +import com.zeapo.pwdstore.R + +object BiometricAuthenticator { + private const val TAG = "BiometricAuthenticator" + private val handler = Handler() + + sealed class Result { + data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result() + data class Failure(val code: Int?, val message: CharSequence) : Result() + object HardwareUnavailableOrDisabled : Result() + object Cancelled : Result() + } + + fun authenticate( + activity: FragmentActivity, + @StringRes dialogTitleRes: Int = R.string.biometric_prompt_title, + callback: (Result) -> Unit + ) { + val authCallback = object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + tag(TAG).d { "BiometricAuthentication error: errorCode=$errorCode, msg=$errString" } + callback(when (errorCode) { + BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED, + BiometricConstants.ERROR_NEGATIVE_BUTTON -> { + Result.Cancelled + } + BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE, + BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> { + Result.HardwareUnavailableOrDisabled + } + 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))) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + callback(Result.Success(result.cryptoObject)) + } + } + val biometricPrompt = BiometricPrompt(activity, { handler.post(it) }, authCallback) + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(activity.getString(dialogTitleRes)) + .setDeviceCredentialAllowed(true) + .build() + if (BiometricManager.from(activity).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || + activity.getSystemService()?.isDeviceSecure == true) { + biometricPrompt.authenticate(promptInfo) + } else { + callback(Result.HardwareUnavailableOrDisabled) + } + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt b/app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt deleted file mode 100644 index 6bde6360..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/auth/AuthenticationResult.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils.auth - -import androidx.biometric.BiometricPrompt - -internal sealed class AuthenticationResult { - internal data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : - AuthenticationResult() - - internal data class RecoverableError(val code: Int, val message: CharSequence) : - AuthenticationResult() - - internal data class UnrecoverableError(val code: Int, val message: CharSequence) : - AuthenticationResult() - - internal object Failure : AuthenticationResult() - internal object Cancelled : AuthenticationResult() -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt deleted file mode 100644 index 34fc9455..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/auth/Authenticator.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils.auth - -import android.os.Handler -import androidx.biometric.BiometricManager -import androidx.biometric.BiometricPrompt -import androidx.fragment.app.FragmentActivity -import com.github.ajalt.timberkt.Timber.tag -import com.github.ajalt.timberkt.d -import com.zeapo.pwdstore.R - -internal class Authenticator( - private val fragmentActivity: FragmentActivity, - private val callback: (AuthenticationResult) -> Unit -) { - private val handler = Handler() - private val biometricManager = BiometricManager.from(fragmentActivity) - - private val authCallback = object : BiometricPrompt.AuthenticationCallback() { - - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - super.onAuthenticationError(errorCode, errString) - tag(TAG).d { "Error: $errorCode: $errString" } - callback(AuthenticationResult.UnrecoverableError(errorCode, errString)) - } - - override fun onAuthenticationFailed() { - super.onAuthenticationFailed() - tag(TAG).d { "Failed" } - callback(AuthenticationResult.Failure) - } - - override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - tag(TAG).d { "Success" } - callback(AuthenticationResult.Success(result.cryptoObject)) - } - } - - private val biometricPrompt = BiometricPrompt( - fragmentActivity, - { runnable -> handler.post(runnable) }, - authCallback - ) - - private val promptInfo = BiometricPrompt.PromptInfo.Builder() - .setTitle(fragmentActivity.getString(R.string.biometric_prompt_title)) - .setDeviceCredentialAllowed(true) - .build() - - fun authenticate() { - if (biometricManager.canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) { - callback(AuthenticationResult.UnrecoverableError( - 0, - fragmentActivity.getString(R.string.biometric_prompt_no_hardware) - )) - } else { - biometricPrompt.authenticate(promptInfo) - } - } - - companion object { - private const val TAG = "Authenticator" - } -} diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 07c4dca7..9e192c1d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -296,9 +296,6 @@ Вы выбрали корень вашей sd-карты для хранения. Это очень опасно и вы потеряете ваши данные, поскольку они будут в конечном итоге удалены Прервать и записать изменения Запрос биометрии - Повторить - Аутентификация отменена - Биометрические сенсоры не обнаружены Включить биометрическую аутентификацию Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения Сенсор отпечатка пальца не доступен или отсутствует diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33c0b103..37dbfef6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,9 +326,8 @@ You have selected the root of your sdcard for the store. This is extremely dangerous and you will lose your data as its content will, eventually, be deleted Abort and Push Biometric Prompt - Retry - Authentication canceled - No Biometric hardware was found + Authentication failure + Authentication failure: %s Enable biometric authentication When enabled, Password Store will prompt you for your fingerprint when launching the app Fingerprint hardware not accessible or missing -- cgit v1.2.3