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.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.zeapo.pwdstore.crypto.PgpActivity
|
import com.zeapo.pwdstore.crypto.PgpActivity
|
||||||
import com.zeapo.pwdstore.utils.auth.AuthenticationResult
|
import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
||||||
import com.zeapo.pwdstore.utils.auth.Authenticator
|
|
||||||
|
|
||||||
class LaunchActivity : AppCompatActivity() {
|
class LaunchActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -19,18 +19,20 @@ class LaunchActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
if (prefs.getBoolean("biometric_auth", false)) {
|
if (prefs.getBoolean("biometric_auth", false)) {
|
||||||
Authenticator(this) {
|
BiometricAuthenticator.authenticate(this) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is AuthenticationResult.Success -> {
|
is BiometricAuthenticator.Result.Success -> {
|
||||||
startTargetActivity(false)
|
startTargetActivity(false)
|
||||||
}
|
}
|
||||||
is AuthenticationResult.UnrecoverableError -> {
|
is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
||||||
|
prefs.edit { remove("biometric_auth") }
|
||||||
|
startTargetActivity(false)
|
||||||
|
}
|
||||||
|
is BiometricAuthenticator.Result.Failure, BiometricAuthenticator.Result.Cancelled -> {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.authenticate()
|
|
||||||
} else {
|
} else {
|
||||||
startTargetActivity(true)
|
startTargetActivity(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,8 @@ import com.zeapo.pwdstore.git.GitServerConfigActivity
|
||||||
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
|
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
|
||||||
import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment
|
import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment
|
||||||
import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity
|
import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity
|
||||||
|
import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
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.autofillManager
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -297,9 +296,9 @@ class UserPreference : AppCompatActivity() {
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
sharedPreferences.edit {
|
sharedPreferences.edit {
|
||||||
val checked = isChecked
|
val checked = isChecked
|
||||||
Authenticator(requireActivity()) { result ->
|
BiometricAuthenticator.authenticate(requireActivity()) { result ->
|
||||||
when (result) {
|
when (result) {
|
||||||
is AuthenticationResult.Success -> {
|
is BiometricAuthenticator.Result.Success -> {
|
||||||
// Apply the changes
|
// Apply the changes
|
||||||
putBoolean("biometric_auth", checked)
|
putBoolean("biometric_auth", checked)
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
@ -312,7 +311,7 @@ class UserPreference : AppCompatActivity() {
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.authenticate()
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
requireContext().getSystemService<ShortcutManager>()?.apply {
|
requireContext().getSystemService<ShortcutManager>()?.apply {
|
||||||
removeDynamicShortcuts(dynamicShortcuts.map { it.id }.toMutableList())
|
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="sdcard_root_warning_message">Вы выбрали корень вашей sd-карты для хранения. Это очень опасно и вы потеряете ваши данные, поскольку они будут в конечном итоге удалены</string>
|
||||||
<string name="git_abort_and_push_title">Прервать и записать изменения</string>
|
<string name="git_abort_and_push_title">Прервать и записать изменения</string>
|
||||||
<string name="biometric_prompt_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_title">Включить биометрическую аутентификацию</string>
|
||||||
<string name="biometric_auth_summary">Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения</string>
|
<string name="biometric_auth_summary">Когда ключено, Password Store будет запрашивать ваш опечаток пальца при каждом запуске приложения</string>
|
||||||
<string name="biometric_auth_summary_error">Сенсор отпечатка пальца не доступен или отсутствует</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="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="git_abort_and_push_title">Abort and Push</string>
|
||||||
<string name="biometric_prompt_title">Biometric Prompt</string>
|
<string name="biometric_prompt_title">Biometric Prompt</string>
|
||||||
<string name="biometric_prompt_retry">Retry</string>
|
<string name="biometric_auth_error">Authentication failure</string>
|
||||||
<string name="biometric_prompt_cancelled">Authentication canceled</string>
|
<string name="biometric_auth_error_reason">Authentication failure: %s</string>
|
||||||
<string name="biometric_prompt_no_hardware">No Biometric hardware was found</string>
|
|
||||||
<string name="biometric_auth_title">Enable biometric authentication</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">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>
|
<string name="biometric_auth_summary_error">Fingerprint hardware not accessible or missing</string>
|
||||||
|
|
Loading…
Reference in a new issue