diff options
7 files changed, 90 insertions, 108 deletions
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<ShortcutManager>()?.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<KeyguardManager>()?.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 @@ <string name="sdcard_root_warning_message">Вы выбрали корень вашей sd-карты для хранения. Это очень опасно и вы потеряете ваши данные, поскольку они будут в конечном итоге удалены</string> <string name="git_abort_and_push_title">Прервать и записать изменения</string> <string name="biometric_prompt_title">Запрос биометрии</string> - <string name="biometric_prompt_retry">Повторить</string> - <string name="biometric_prompt_cancelled">Аутентификация отменена</string> - <string name="biometric_prompt_no_hardware">Биометрические сенсоры не обнаружены</string> <string name="biometric_auth_title">Включить биометрическую аутентификацию</string> <string name="biometric_auth_summary">Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения</string> <string name="biometric_auth_summary_error">Сенсор отпечатка пальца не доступен или отсутствует</string> 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 @@ <string name="sdcard_root_warning_message">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</string> <string name="git_abort_and_push_title">Abort and Push</string> <string name="biometric_prompt_title">Biometric Prompt</string> - <string name="biometric_prompt_retry">Retry</string> - <string name="biometric_prompt_cancelled">Authentication canceled</string> - <string name="biometric_prompt_no_hardware">No Biometric hardware was found</string> + <string name="biometric_auth_error">Authentication failure</string> + <string name="biometric_auth_error_reason">Authentication failure: %s</string> <string name="biometric_auth_title">Enable biometric authentication</string> <string name="biometric_auth_summary">When enabled, Password Store will prompt you for your fingerprint when launching the app</string> <string name="biometric_auth_summary_error">Fingerprint hardware not accessible or missing</string> |