BiometricAuthenticator: rework logic and bugs

Otherwise there's a frameworks bug that causes the fragment's activity
to become null.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2020-03-30 00:45:41 -06:00
parent d2721f2d7d
commit 09b40cdec7
3 changed files with 39 additions and 15 deletions

View File

@ -4,6 +4,7 @@
*/
package com.wireguard.android.fragment
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.text.InputType
@ -83,7 +84,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
}
override fun onDestroyView() {
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
binding = null
super.onDestroyView()
}
@ -106,7 +107,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
InputMethodManager.HIDE_NOT_ALWAYS)
}
// Tell the activity to finish itself or go back to the detail view.
requireActivity().runOnUiThread {
activity.runOnUiThread {
// TODO(smaeul): Remove this hack when fixing the Config ViewModel
// The selected tunnel has to actually change, but we have to remember this one.
val savedTunnel = tunnel
@ -228,13 +229,17 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
super.onViewStateRestored(savedInstanceState)
}
private var showingAuthenticator = false
fun onKeyClick(view: View) = onKeyFocusChange(view, true)
fun onKeyFocusChange(view: View, isFocused: Boolean) {
if (!isFocused) return
if (!isFocused || showingAuthenticator) return
val edit = view as? EditText ?: return
if (!haveShownKeys && edit.text.isNotEmpty()) {
BiometricAuthenticator.authenticate(R.string.biometric_prompt_private_key_title, requireActivity()) {
showingAuthenticator = true
BiometricAuthenticator.authenticate(R.string.biometric_prompt_private_key_title, this) {
showingAuthenticator = false
when (it) {
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
haveShownKeys = true
@ -255,7 +260,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
}
private fun showPrivateKey(edit: EditText) {
requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
edit.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
}

View File

@ -83,7 +83,8 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
override fun onClick() {
val prefActivity = FragmentUtils.getPrefActivity(this)
BiometricAuthenticator.authenticate(R.string.biometric_prompt_zip_exporter_title, prefActivity) {
val fragment = prefActivity.supportFragmentManager.fragments.first()
BiometricAuthenticator.authenticate(R.string.biometric_prompt_zip_exporter_title, fragment) {
when (it) {
// When we have successful authentication, or when there is no biometric hardware available.
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {

View File

@ -5,15 +5,21 @@
package com.wireguard.android.util
import android.annotation.SuppressLint
import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import android.os.Handler
import android.util.Log
import androidx.annotation.StringRes
import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import com.wireguard.android.R
object BiometricAuthenticator {
private const val TAG = "WireGuard/BiometricAuthenticator"
private val handler = Handler()
@ -25,12 +31,25 @@ object BiometricAuthenticator {
object Cancelled : Result()
}
@SuppressLint("PrivateApi")
private fun isPinEnabled(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.getSystemService<KeyguardManager>()!!.isDeviceSecure
return try {
val lockUtilsClass = Class.forName("com.android.internal.widget.LockPatternUtils")
val lockUtils = lockUtilsClass.getConstructor(Context::class.java).newInstance(context)
val method = lockUtilsClass.getMethod("isLockScreenDisabled")
!(method.invoke(lockUtils) as Boolean)
} catch (e: Exception) {
false
}
}
fun authenticate(
@StringRes dialogTitleRes: Int,
fragmentActivity: FragmentActivity,
fragment: Fragment,
callback: (Result) -> Unit
) {
val biometricManager = BiometricManager.from(fragmentActivity)
val authCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
@ -44,13 +63,13 @@ object BiometricAuthenticator {
BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
Result.HardwareUnavailableOrDisabled
}
else -> Result.Failure(errorCode, fragmentActivity.getString(R.string.biometric_auth_error_reason, errString))
else -> Result.Failure(errorCode, fragment.getString(R.string.biometric_auth_error_reason, errString))
})
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
callback(Result.Failure(null, fragmentActivity.getString(R.string.biometric_auth_error)))
callback(Result.Failure(null, fragment.getString(R.string.biometric_auth_error)))
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
@ -58,13 +77,12 @@ object BiometricAuthenticator {
callback(Result.Success(result.cryptoObject))
}
}
val biometricPrompt = BiometricPrompt(fragmentActivity, { handler.post(it) }, authCallback)
val biometricPrompt = BiometricPrompt(fragment, { handler.post(it) }, authCallback)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(fragmentActivity.getString(dialogTitleRes))
.setTitle(fragment.getString(dialogTitleRes))
.setDeviceCredentialAllowed(true)
.build()
if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
if (BiometricManager.from(fragment.requireContext()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isPinEnabled(fragment.requireContext())) {
biometricPrompt.authenticate(promptInfo)
} else {
callback(Result.HardwareUnavailableOrDisabled)