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:
parent
d2721f2d7d
commit
09b40cdec7
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.wireguard.android.fragment
|
package com.wireguard.android.fragment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
@ -83,7 +84,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
binding = null
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
@ -106,7 +107,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
|||||||
InputMethodManager.HIDE_NOT_ALWAYS)
|
InputMethodManager.HIDE_NOT_ALWAYS)
|
||||||
}
|
}
|
||||||
// Tell the activity to finish itself or go back to the detail view.
|
// 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
|
// TODO(smaeul): Remove this hack when fixing the Config ViewModel
|
||||||
// The selected tunnel has to actually change, but we have to remember this one.
|
// The selected tunnel has to actually change, but we have to remember this one.
|
||||||
val savedTunnel = tunnel
|
val savedTunnel = tunnel
|
||||||
@ -228,13 +229,17 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
|||||||
super.onViewStateRestored(savedInstanceState)
|
super.onViewStateRestored(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var showingAuthenticator = false
|
||||||
|
|
||||||
fun onKeyClick(view: View) = onKeyFocusChange(view, true)
|
fun onKeyClick(view: View) = onKeyFocusChange(view, true)
|
||||||
|
|
||||||
fun onKeyFocusChange(view: View, isFocused: Boolean) {
|
fun onKeyFocusChange(view: View, isFocused: Boolean) {
|
||||||
if (!isFocused) return
|
if (!isFocused || showingAuthenticator) return
|
||||||
val edit = view as? EditText ?: return
|
val edit = view as? EditText ?: return
|
||||||
if (!haveShownKeys && edit.text.isNotEmpty()) {
|
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) {
|
when (it) {
|
||||||
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
||||||
haveShownKeys = true
|
haveShownKeys = true
|
||||||
@ -255,7 +260,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showPrivateKey(edit: EditText) {
|
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
|
edit.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,8 @@ class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference
|
|||||||
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
val prefActivity = FragmentUtils.getPrefActivity(this)
|
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 (it) {
|
||||||
// When we have successful authentication, or when there is no biometric hardware available.
|
// When we have successful authentication, or when there is no biometric hardware available.
|
||||||
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
is BiometricAuthenticator.Result.Success, is BiometricAuthenticator.Result.HardwareUnavailableOrDisabled -> {
|
||||||
|
@ -5,15 +5,21 @@
|
|||||||
|
|
||||||
package com.wireguard.android.util
|
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.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.biometric.BiometricConstants
|
import androidx.biometric.BiometricConstants
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import com.wireguard.android.R
|
import com.wireguard.android.R
|
||||||
|
|
||||||
|
|
||||||
object BiometricAuthenticator {
|
object BiometricAuthenticator {
|
||||||
private const val TAG = "WireGuard/BiometricAuthenticator"
|
private const val TAG = "WireGuard/BiometricAuthenticator"
|
||||||
private val handler = Handler()
|
private val handler = Handler()
|
||||||
@ -25,12 +31,25 @@ object BiometricAuthenticator {
|
|||||||
object Cancelled : Result()
|
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(
|
fun authenticate(
|
||||||
@StringRes dialogTitleRes: Int,
|
@StringRes dialogTitleRes: Int,
|
||||||
fragmentActivity: FragmentActivity,
|
fragment: Fragment,
|
||||||
callback: (Result) -> Unit
|
callback: (Result) -> Unit
|
||||||
) {
|
) {
|
||||||
val biometricManager = BiometricManager.from(fragmentActivity)
|
|
||||||
val authCallback = object : BiometricPrompt.AuthenticationCallback() {
|
val authCallback = object : BiometricPrompt.AuthenticationCallback() {
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
super.onAuthenticationError(errorCode, errString)
|
super.onAuthenticationError(errorCode, errString)
|
||||||
@ -44,13 +63,13 @@ object BiometricAuthenticator {
|
|||||||
BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
|
BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> {
|
||||||
Result.HardwareUnavailableOrDisabled
|
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() {
|
override fun onAuthenticationFailed() {
|
||||||
super.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) {
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
@ -58,13 +77,12 @@ object BiometricAuthenticator {
|
|||||||
callback(Result.Success(result.cryptoObject))
|
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()
|
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
.setTitle(fragmentActivity.getString(dialogTitleRes))
|
.setTitle(fragment.getString(dialogTitleRes))
|
||||||
.setDeviceCredentialAllowed(true)
|
.setDeviceCredentialAllowed(true)
|
||||||
.build()
|
.build()
|
||||||
|
if (BiometricManager.from(fragment.requireContext()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isPinEnabled(fragment.requireContext())) {
|
||||||
if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
|
|
||||||
biometricPrompt.authenticate(promptInfo)
|
biometricPrompt.authenticate(promptInfo)
|
||||||
} else {
|
} else {
|
||||||
callback(Result.HardwareUnavailableOrDisabled)
|
callback(Result.HardwareUnavailableOrDisabled)
|
||||||
|
Loading…
Reference in New Issue
Block a user