auth: redo implementation with a cleaner and simpler API surface (#741)

This commit is contained in:
Harsh Shandilya 2020-04-24 15:00:33 +05:30 committed by GitHub
parent bee20ac44a
commit 73695e2493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 108 deletions

View file

@ -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)
}

View file

@ -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())

View file

@ -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)
}
}
}

View file

@ -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()
}

View file

@ -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"
}
}

View file

@ -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>

View file

@ -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>