Adding lockout coordinator

Adding the building blocks for the lockout coordinator. This
class will be used to manage & coordinate lockouts/resetLockouts across
users/biometrics.

Test: atest AuthResultCoordinatorTest AuthSessionCoordinatorTest
MultiBiometricLockoutStateTest
Bug: 244355277

Change-Id: I1ffbd97eb06687f83f85f0c7b57a1bef65e0b141
This commit is contained in:
Joshua McCloskey
2022-09-12 15:56:19 +00:00
parent f411c0c1f8
commit 51d34ccac2
8 changed files with 798 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import android.hardware.biometrics.BiometricManager;
class AuthResult {
static final int FAILED = 0;
static final int LOCKED_OUT = 1;
static final int AUTHENTICATED = 2;
private final int mStatus;
private final int mBiometricStrength;
AuthResult(int status, @BiometricManager.Authenticators.Types int strength) {
mStatus = status;
mBiometricStrength = strength;
}
int getStatus() {
return mStatus;
}
int getBiometricStrength() {
return mBiometricStrength;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import android.hardware.biometrics.BiometricManager.Authenticators;
import java.util.ArrayList;
import java.util.List;
/**
* A class that takes in a series of authentication attempts (successes, failures, lockouts)
* across different biometric strengths (convenience, weak, strong) and returns a single AuthResult.
*
* The AuthResult will be the strongest biometric operation that occurred amongst all reported
* operations, and if multiple such operations exist, it will favor a successful authentication.
*/
class AuthResultCoordinator {
private static final String TAG = "AuthResultCoordinator";
private final List<AuthResult> mOperations;
AuthResultCoordinator() {
mOperations = new ArrayList<>();
}
/**
* Adds auth success for a given strength to the current operation list.
*/
void authenticatedFor(@Authenticators.Types int strength) {
mOperations.add(new AuthResult(AuthResult.AUTHENTICATED, strength));
}
/**
* Adds auth ended for a given strength to the current operation list.
*/
void authEndedFor(@Authenticators.Types int strength) {
mOperations.add(new AuthResult(AuthResult.FAILED, strength));
}
/**
* Adds a lock out of a given strength to the current operation list.
*/
void lockedOutFor(@Authenticators.Types int strength) {
mOperations.add(new AuthResult(AuthResult.LOCKED_OUT, strength));
}
/**
* Obtains an auth result & strength from a current set of biometric operations.
*/
AuthResult getResult() {
AuthResult result = new AuthResult(AuthResult.FAILED, Authenticators.BIOMETRIC_CONVENIENCE);
return mOperations.stream().filter(
(element) -> element.getStatus() != AuthResult.FAILED).reduce(result,
((curr, next) -> {
int strengthCompare = curr.getBiometricStrength() - next.getBiometricStrength();
if (strengthCompare < 0) {
return curr;
} else if (strengthCompare == 0) {
// Equal level of strength, favor authentication.
if (curr.getStatus() == AuthResult.AUTHENTICATED) {
return curr;
} else {
// Either next is Authenticated, or it is not, either way return this
// one.
return next;
}
} else {
// curr is a weaker biometric
return next;
}
}));
}
void resetState() {
mOperations.clear();
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.util.Slog;
import java.util.HashSet;
import java.util.Set;
/**
* Coordinates lockout counter enforcement for all types of biometric strengths across all users.
*
* This class is not thread-safe. In general, all calls to this class should be made on the same
* handler to ensure no collisions.
*/
class AuthSessionCoordinator implements AuthSessionListener {
private static final String TAG = "AuthSessionCoordinator";
private final Set<Integer> mAuthOperations;
private int mUserId;
private boolean mIsAuthenticating;
private AuthResultCoordinator mAuthResultCoordinator;
private MultiBiometricLockoutState mMultiBiometricLockoutState;
AuthSessionCoordinator() {
mAuthOperations = new HashSet<>();
mAuthResultCoordinator = new AuthResultCoordinator();
mMultiBiometricLockoutState = new MultiBiometricLockoutState();
}
/**
* A Call indicating that an auth session has started
*/
void onAuthSessionStarted(int userId) {
mAuthOperations.clear();
mUserId = userId;
mIsAuthenticating = true;
mAuthResultCoordinator.resetState();
}
/**
* Ends the current auth session and updates the lockout state.
*
* This can happen two ways.
* 1. Manually calling this API
* 2. If authStartedFor() was called, and all authentication attempts finish.
*/
void endAuthSession() {
if (mIsAuthenticating) {
mAuthOperations.clear();
AuthResult res =
mAuthResultCoordinator.getResult();
if (res.getStatus() == AuthResult.AUTHENTICATED) {
mMultiBiometricLockoutState.onUserUnlocked(mUserId, res.getBiometricStrength());
} else if (res.getStatus() == AuthResult.LOCKED_OUT) {
mMultiBiometricLockoutState.onUserLocked(mUserId, res.getBiometricStrength());
}
mAuthResultCoordinator.resetState();
mIsAuthenticating = false;
}
}
/**
* @return true if a user can authenticate with a given strength.
*/
boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength);
}
@Override
public void authStartedFor(int userId, int sensorId) {
if (!mIsAuthenticating) {
onAuthSessionStarted(userId);
}
if (mAuthOperations.contains(sensorId)) {
Slog.e(TAG, "Error, authStartedFor(" + sensorId + ") without being finished");
return;
}
if (mUserId != userId) {
Slog.e(TAG, "Error authStartedFor(" + userId + ") Incorrect userId, expected" + mUserId
+ ", ignoring...");
return;
}
mAuthOperations.add(sensorId);
}
@Override
public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength,
int sensorId) {
mAuthResultCoordinator.authenticatedFor(biometricStrength);
attemptToFinish(userId, sensorId,
"authenticatedFor(userId=" + userId + ", biometricStrength=" + biometricStrength
+ ", sensorId=" + sensorId + "");
}
@Override
public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength,
int sensorId) {
mAuthResultCoordinator.lockedOutFor(biometricStrength);
attemptToFinish(userId, sensorId,
"lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength
+ ", sensorId=" + sensorId + "");
}
@Override
public void authEndedFor(int userId, @Authenticators.Types int biometricStrength,
int sensorId) {
mAuthResultCoordinator.authEndedFor(biometricStrength);
attemptToFinish(userId, sensorId,
"authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
+ ", sensorId=" + sensorId);
}
@Override
public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength) {
mMultiBiometricLockoutState.onUserUnlocked(userId, biometricStrength);
}
private void attemptToFinish(int userId, int sensorId, String description) {
boolean didFail = false;
if (!mAuthOperations.contains(sensorId)) {
Slog.e(TAG, "Error unable to find auth operation : " + description);
didFail = true;
}
if (userId != mUserId) {
Slog.e(TAG, "Error mismatched userId, expected=" + mUserId + " for " + description);
didFail = true;
}
if (didFail) {
return;
}
mAuthOperations.remove(sensorId);
if (mIsAuthenticating && mAuthOperations.isEmpty()) {
endAuthSession();
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import android.hardware.biometrics.BiometricManager.Authenticators;
/**
* An interface that listens to authentication events.
*/
interface AuthSessionListener {
/**
* Indicates an auth operation has started for a given user and sensor.
*/
void authStartedFor(int userId, int sensorId);
/**
* Indicates a successful authentication occurred for a sensor of a given strength.
*/
void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);
/**
* Indicates authentication ended for a sensor of a given strength.
*/
void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);
/**
* Indicates a lockout occurred for a sensor of a given strength.
*/
void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);
/**
* Indicates that a reset lockout has happened for a given strength.
*/
void resetLockoutFor(int uerId, @Authenticators.Types int biometricStrength);
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class is used as a system to store the state of each
* {@link Authenticators.Types} status for every user.
*/
class MultiBiometricLockoutState {
private static final String TAG = "MultiBiometricLockoutState";
private static final Map<Integer, List<Integer>> PRECEDENCE;
static {
Map<Integer, List<Integer>> precedence = new ArrayMap<>();
precedence.put(Authenticators.BIOMETRIC_STRONG,
Arrays.asList(BIOMETRIC_STRONG, BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE));
precedence.put(BIOMETRIC_WEAK, Arrays.asList(BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE));
precedence.put(BIOMETRIC_CONVENIENCE, Arrays.asList(BIOMETRIC_CONVENIENCE));
PRECEDENCE = Collections.unmodifiableMap(precedence);
}
private final Map<Integer, Map<Integer, Boolean>> mCanUserAuthenticate;
@VisibleForTesting
MultiBiometricLockoutState() {
mCanUserAuthenticate = new HashMap<>();
}
private static Map<Integer, Boolean> createLockedOutMap() {
Map<Integer, Boolean> lockOutMap = new HashMap<>();
lockOutMap.put(BIOMETRIC_STRONG, false);
lockOutMap.put(BIOMETRIC_WEAK, false);
lockOutMap.put(BIOMETRIC_CONVENIENCE, false);
return lockOutMap;
}
private Map<Integer, Boolean> getAuthMapForUser(int userId) {
if (!mCanUserAuthenticate.containsKey(userId)) {
mCanUserAuthenticate.put(userId, createLockedOutMap());
}
return mCanUserAuthenticate.get(userId);
}
/**
* Indicates a {@link Authenticators} has been locked for userId.
*
* @param userId The user.
* @param strength The strength of biometric that is requested to be locked.
*/
void onUserLocked(int userId, @Authenticators.Types int strength) {
Slog.d(TAG, "onUserLocked(userId=" + userId + ", strength=" + strength + ")");
Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId);
for (int strengthToLockout : PRECEDENCE.get(strength)) {
canUserAuthState.put(strengthToLockout, false);
}
}
/**
* Indicates that a user has unlocked a {@link Authenticators}
*
* @param userId The user.
* @param strength The strength of biometric that is unlocked.
*/
void onUserUnlocked(int userId, @Authenticators.Types int strength) {
Slog.d(TAG, "onUserUnlocked(userId=" + userId + ", strength=" + strength + ")");
Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId);
for (int strengthToLockout : PRECEDENCE.get(strength)) {
canUserAuthState.put(strengthToLockout, true);
}
}
/**
* Indicates if a user can perform an authentication operation with a given
* {@link Authenticators.Types}
*
* @param userId The user.
* @param strength The strength of biometric that is requested to authenticate.
* @return If a user can authenticate with a given biometric of this strength.
*/
boolean canUserAuthenticate(int userId, @Authenticators.Types int strength) {
final boolean canAuthenticate = getAuthMapForUser(userId).get(strength);
Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") ="
+ canAuthenticate);
return canAuthenticate;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import static com.google.common.truth.Truth.assertThat;
import android.hardware.biometrics.BiometricManager;
import org.junit.Before;
import org.junit.Test;
public class AuthResultCoordinatorTest {
private AuthResultCoordinator mAuthResultCoordinator;
@Before
public void setUp() throws Exception {
mAuthResultCoordinator = new AuthResultCoordinator();
}
@Test
public void testDefaultMessage() {
checkResult(mAuthResultCoordinator.getResult(),
AuthResult.FAILED,
BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
}
@Test
public void testSingleMessageCoordinator() {
mAuthResultCoordinator.authenticatedFor(
BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
checkResult(mAuthResultCoordinator.getResult(),
AuthResult.AUTHENTICATED,
BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
}
@Test
public void testLockout() {
mAuthResultCoordinator.lockedOutFor(
BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
checkResult(mAuthResultCoordinator.getResult(),
AuthResult.LOCKED_OUT,
BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
}
@Test
public void testHigherStrengthPrecedence() {
mAuthResultCoordinator.authenticatedFor(
BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
mAuthResultCoordinator.authenticatedFor(
BiometricManager.Authenticators.BIOMETRIC_WEAK);
checkResult(mAuthResultCoordinator.getResult(),
AuthResult.AUTHENTICATED,
BiometricManager.Authenticators.BIOMETRIC_WEAK);
mAuthResultCoordinator.authenticatedFor(
BiometricManager.Authenticators.BIOMETRIC_STRONG);
checkResult(mAuthResultCoordinator.getResult(),
AuthResult.AUTHENTICATED,
BiometricManager.Authenticators.BIOMETRIC_STRONG);
}
@Test
public void testAuthPrecedence() {
mAuthResultCoordinator.authenticatedFor(
BiometricManager.Authenticators.BIOMETRIC_WEAK);
mAuthResultCoordinator.lockedOutFor(
BiometricManager.Authenticators.BIOMETRIC_WEAK);
checkResult(mAuthResultCoordinator.getResult(),
AuthResult.AUTHENTICATED,
BiometricManager.Authenticators.BIOMETRIC_WEAK);
}
void checkResult(AuthResult res, int status,
@BiometricManager.Authenticators.Types int strength) {
assertThat(res.getStatus()).isEqualTo(status);
assertThat(res.getBiometricStrength()).isEqualTo(strength);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import static com.google.common.truth.Truth.assertThat;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
@Presubmit
@SmallTest
public class AuthSessionCoordinatorTest {
private static final int PRIMARY_USER = 0;
private static final int SECONDARY_USER = 10;
private AuthSessionCoordinator mCoordinator;
@Before
public void setUp() throws Exception {
mCoordinator = new AuthSessionCoordinator();
}
@Test
public void testUserUnlocked() {
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
mCoordinator.authStartedFor(PRIMARY_USER, 1);
mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1);
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
}
@Test
public void testUserCanAuthDuringLockoutOfSameSession() {
mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG);
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
mCoordinator.authStartedFor(PRIMARY_USER, 1);
mCoordinator.authStartedFor(PRIMARY_USER, 2);
mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2);
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
}
@Test
public void testMultiUserAuth() {
mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG);
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
mCoordinator.authStartedFor(PRIMARY_USER, 1);
mCoordinator.authStartedFor(PRIMARY_USER, 2);
mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2);
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import static com.google.common.truth.Truth.assertThat;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MultiBiometricLockoutStateTest {
private static final int PRIMARY_USER = 0;
private MultiBiometricLockoutState mCoordinator;
private void unlockAllBiometrics() {
unlockAllBiometrics(mCoordinator, PRIMARY_USER);
}
private void lockoutAllBiometrics() {
lockoutAllBiometrics(mCoordinator, PRIMARY_USER);
}
private static void unlockAllBiometrics(MultiBiometricLockoutState coordinator, int userId) {
coordinator.onUserUnlocked(userId, BIOMETRIC_STRONG);
assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isTrue();
assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isTrue();
assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isTrue();
}
private static void lockoutAllBiometrics(MultiBiometricLockoutState coordinator, int userId) {
coordinator.onUserLocked(userId, BIOMETRIC_STRONG);
assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isFalse();
assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isFalse();
assertThat(coordinator.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isFalse();
}
@Before
public void setUp() throws Exception {
mCoordinator = new MultiBiometricLockoutState();
}
@Test
public void testInitialStateLockedOut() {
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
}
@Test
public void testConvenienceLockout() {
unlockAllBiometrics();
mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_CONVENIENCE);
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
}
@Test
public void testWeakLockout() {
unlockAllBiometrics();
mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_WEAK);
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
}
@Test
public void testStrongLockout() {
unlockAllBiometrics();
mCoordinator.onUserLocked(PRIMARY_USER, BIOMETRIC_STRONG);
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
}
@Test
public void testConvenienceUnlock() {
lockoutAllBiometrics();
mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_CONVENIENCE);
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
}
@Test
public void testWeakUnlock() {
lockoutAllBiometrics();
mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_WEAK);
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
}
@Test
public void testStrongUnlock() {
lockoutAllBiometrics();
mCoordinator.onUserUnlocked(PRIMARY_USER, BIOMETRIC_STRONG);
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
assertThat(mCoordinator.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
}
@Test
public void multiUser_userOneDoesNotAffectUserTwo() {
final int userOne = 1;
final int userTwo = 2;
MultiBiometricLockoutState coordinator = new MultiBiometricLockoutState();
lockoutAllBiometrics(coordinator, userOne);
lockoutAllBiometrics(coordinator, userTwo);
coordinator.onUserUnlocked(userOne, BIOMETRIC_WEAK);
assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_STRONG)).isFalse();
assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_WEAK)).isTrue();
assertThat(coordinator.canUserAuthenticate(userOne, BIOMETRIC_CONVENIENCE)).isTrue();
assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_STRONG)).isFalse();
assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_WEAK)).isFalse();
assertThat(coordinator.canUserAuthenticate(userTwo, BIOMETRIC_CONVENIENCE)).isFalse();
}
}