[local_auth] Fix device credential only check for API < 30 (#6522)
diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md
index a26846d..c9eeed9 100644
--- a/packages/local_auth/local_auth_android/CHANGELOG.md
+++ b/packages/local_auth/local_auth_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.0.14
+
+* Fixes device credential authentication for API versions before R.
+
## 1.0.13
* Updates imports for `prefer_relative_imports`.
diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle
index 569d7e3..6c94170 100644
--- a/packages/local_auth/local_auth_android/android/build.gradle
+++ b/packages/local_auth/local_auth_android/android/build.gradle
@@ -56,6 +56,7 @@
api "androidx.fragment:fragment:1.5.2"
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:4.7.0'
+ testImplementation 'org.robolectric:robolectric:4.5'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
diff --git a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
index e8632c4..e545df0 100644
--- a/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
+++ b/packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java
@@ -253,11 +253,16 @@
}
@VisibleForTesting
- public boolean isDeviceSupported() {
+ public boolean isDeviceSecure() {
if (keyguardManager == null) return false;
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure());
}
+ @VisibleForTesting
+ public boolean isDeviceSupported() {
+ return isDeviceSecure() || canAuthenticateWithBiometrics();
+ }
+
private boolean canAuthenticateWithBiometrics() {
if (biometricManager == null) return false;
return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
@@ -270,7 +275,15 @@
!= BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
}
- private boolean canAuthenticateWithDeviceCredential() {
+ @VisibleForTesting
+ public boolean canAuthenticateWithDeviceCredential() {
+ if (Build.VERSION.SDK_INT < 30) {
+ // Checking for device credential only authentication via the BiometricManager
+ // is not allowed before API level 30, so we check for presence of PIN, pattern,
+ // or password instead.
+ return isDeviceSecure();
+ }
+
if (biometricManager == null) return false;
return biometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
== BiometricManager.BIOMETRIC_SUCCESS;
@@ -334,4 +347,9 @@
void setBiometricManager(BiometricManager biometricManager) {
this.biometricManager = biometricManager;
}
+
+ @VisibleForTesting
+ void setKeyguardManager(KeyguardManager keyguardManager) {
+ this.keyguardManager = keyguardManager;
+ }
}
diff --git a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java
index 0eaf312..7279a3c 100644
--- a/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java
+++ b/packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java
@@ -17,6 +17,7 @@
import static org.mockito.Mockito.when;
import android.app.Activity;
+import android.app.KeyguardManager;
import android.app.NativeActivity;
import android.content.Context;
import androidx.biometric.BiometricManager;
@@ -34,8 +35,12 @@
import java.util.Collections;
import java.util.HashMap;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+@RunWith(RobolectricTestRunner.class)
public class LocalAuthTest {
@Test
public void authenticate_returnsErrorWhenAuthInProgress() {
@@ -107,14 +112,13 @@
}
@Test
+ @Config(sdk = 30)
public void authenticate_properlyConfiguresBiometricAndDeviceCredentialAuthenticationRequest() {
final LocalAuthPlugin plugin = spy(new LocalAuthPlugin());
setPluginActivity(plugin, buildMockActivityWithContext(mock(FragmentActivity.class)));
when(plugin.isDeviceSupported()).thenReturn(true);
final BiometricManager mockBiometricManager = mock(BiometricManager.class);
- when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK))
- .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL))
.thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
plugin.setBiometricManager(mockBiometricManager);
@@ -135,6 +139,7 @@
}
@Test
+ @Config(sdk = 30)
public void authenticate_properlyConfiguresDeviceCredentialOnlyAuthenticationRequest() {
final LocalAuthPlugin plugin = spy(new LocalAuthPlugin());
setPluginActivity(plugin, buildMockActivityWithContext(mock(FragmentActivity.class)));
@@ -343,6 +348,44 @@
});
}
+ @Test
+ @Config(sdk = 22)
+ public void isDeviceSecure_returnsFalseOnBelowApi23() {
+ final LocalAuthPlugin plugin = new LocalAuthPlugin();
+ assertFalse(plugin.isDeviceSecure());
+ }
+
+ @Test
+ @Config(sdk = 23)
+ public void isDeviceSecure_returnsTrueIfDeviceIsSecure() {
+ final LocalAuthPlugin plugin = new LocalAuthPlugin();
+ KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
+ plugin.setKeyguardManager(mockKeyguardManager);
+
+ when(mockKeyguardManager.isDeviceSecure()).thenReturn(true);
+ assertTrue(plugin.isDeviceSecure());
+
+ when(mockKeyguardManager.isDeviceSecure()).thenReturn(false);
+ assertFalse(plugin.isDeviceSecure());
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void
+ canAuthenticateWithDeviceCredential_returnsTrueIfHasBiometricManagerSupportAboveApi30() {
+ final LocalAuthPlugin plugin = new LocalAuthPlugin();
+ final BiometricManager mockBiometricManager = mock(BiometricManager.class);
+ plugin.setBiometricManager(mockBiometricManager);
+
+ when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL))
+ .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
+ assertTrue(plugin.canAuthenticateWithDeviceCredential());
+
+ when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED);
+ assertFalse(plugin.canAuthenticateWithDeviceCredential());
+ }
+
private Activity buildMockActivityWithContext(Activity mockActivity) {
final Context mockContext = mock(Context.class);
when(mockActivity.getBaseContext()).thenReturn(mockContext);
diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml
index 35c2d3a..99e9e2c 100644
--- a/packages/local_auth/local_auth_android/pubspec.yaml
+++ b/packages/local_auth/local_auth_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: Android implementation of the local_auth plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
-version: 1.0.13
+version: 1.0.14
environment:
sdk: ">=2.14.0 <3.0.0"