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

View file

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

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="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>

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="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>