auth: redo implementation with a cleaner and simpler API surface (#741)
This commit is contained in:
parent
bee20ac44a
commit
73695e2493
7 changed files with 90 additions and 108 deletions
|
@ -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 -> {
|
||||
is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
||||
prefs.edit { remove("biometric_auth") }
|
||||
startTargetActivity(false)
|
||||
}
|
||||
is BiometricAuthenticator.Result.Failure, BiometricAuthenticator.Result.Cancelled -> {
|
||||
finish()
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}.authenticate()
|
||||
} else {
|
||||
startTargetActivity(true)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue