blob: acb20edfe8d8500b12a1edc1bdd0d188e3a899bc [file] [log] [blame]
/*
* Copyright (C) 2020 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.locksettings;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.server.locksettings.LockSettingsStrongAuth.DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
import static com.android.server.locksettings.LockSettingsStrongAuth.DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
import static com.android.server.locksettings.LockSettingsStrongAuth.NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG;
import static com.android.server.locksettings.LockSettingsStrongAuth.NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG;
import static com.android.server.locksettings.LockSettingsStrongAuth.STRONG_AUTH_TIMEOUT_ALARM_TAG;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricIdleTimeoutAlarmListener;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricTimeoutAlarmListener;
import com.android.server.locksettings.LockSettingsStrongAuth.StrongAuthTimeoutAlarmListener;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
public class LockSettingsStrongAuthTest {
private static final String TAG = LockSettingsStrongAuthTest.class.getSimpleName();
private static final int PRIMARY_USER_ID = 0;
private LockSettingsStrongAuth mStrongAuth;
private final int mDefaultStrongAuthFlags = STRONG_AUTH_NOT_REQUIRED;
private final boolean mDefaultIsNonStrongBiometricAllowed = true;
@Mock
private Context mContext;
@Mock
private LockSettingsStrongAuth.Injector mInjector;
@Mock
private AlarmManager mAlarmManager;
@Mock
private DevicePolicyManager mDPM;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mInjector.getAlarmManager(mContext)).thenReturn(mAlarmManager);
when(mInjector.getDefaultStrongAuthFlags(mContext)).thenReturn(mDefaultStrongAuthFlags);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDPM);
mStrongAuth = new LockSettingsStrongAuth(mContext, mInjector);
}
@Test
public void testScheduleNonStrongBiometricIdleTimeout() {
final long nextAlarmTime = 1000;
when(mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS))
.thenReturn(nextAlarmTime);
mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
waitForIdle();
NonStrongBiometricIdleTimeoutAlarmListener alarm = mStrongAuth
.mNonStrongBiometricIdleTimeoutAlarmListener.get(PRIMARY_USER_ID);
// verify that a new alarm for idle timeout is added for the user
assertNotNull(alarm);
// verify that the alarm is scheduled
verifyAlarm(nextAlarmTime, NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm);
}
@Test
public void testSetIsNonStrongBiometricAllowed_disallowed() {
mStrongAuth.setIsNonStrongBiometricAllowed(false /* allowed */, PRIMARY_USER_ID);
waitForIdle();
// verify that unlocking with non-strong biometrics is not allowed
assertFalse(mStrongAuth.mIsNonStrongBiometricAllowedForUser
.get(PRIMARY_USER_ID, mDefaultIsNonStrongBiometricAllowed));
}
@Test
public void testReportSuccessfulBiometricUnlock_nonStrongBiometric_fallbackTimeout() {
final long nextAlarmTime = 1000;
when(mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS))
.thenReturn(nextAlarmTime);
mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
waitForIdle();
NonStrongBiometricTimeoutAlarmListener alarm =
mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(PRIMARY_USER_ID);
// verify that a new alarm for fallback timeout is added for the user
assertNotNull(alarm);
// verify that the alarm is scheduled
verifyAlarm(nextAlarmTime, NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm);
}
@Test
public void testRequireStrongAuth_nonStrongBiometric_fallbackTimeout() {
mStrongAuth.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT /* strongAuthReason */,
PRIMARY_USER_ID);
waitForIdle();
// verify that the StrongAuthFlags for the user contains the expected flag
final int expectedFlag = STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
verifyStrongAuthFlags(expectedFlag, PRIMARY_USER_ID);
}
@Test
public void testReportSuccessfulBiometricUnlock_nonStrongBiometric_cancelIdleTimeout() {
// lock device and schedule an alarm for non-strong biometric idle timeout
mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
// unlock with non-strong biometric
mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
waitForIdle();
// verify that the current alarm for idle timeout is cancelled after a successful unlock
verify(mAlarmManager).cancel(any(NonStrongBiometricIdleTimeoutAlarmListener.class));
}
@Test
public void testReportSuccessfulBiometricUnlock_strongBio_cancelAlarmsAndAllowNonStrongBio() {
setupAlarms(PRIMARY_USER_ID);
mStrongAuth.reportSuccessfulBiometricUnlock(true /* isStrongBiometric */, PRIMARY_USER_ID);
waitForIdle();
// verify that unlocking with strong biometric cancels alarms for fallback and idle timeout
// and re-allow unlocking with non-strong biometric
verifyAlarmsCancelledAndNonStrongBiometricAllowed(PRIMARY_USER_ID);
}
@Test
public void testReportSuccessfulStrongAuthUnlock_schedulePrimaryAuthTimeout() {
final long currentTime = 1000;
final long timeout = 1000;
final long nextAlarmTime = currentTime + timeout;
when(mInjector.getElapsedRealtimeMs()).thenReturn(currentTime);
when(mDPM.getRequiredStrongAuthTimeout(null, PRIMARY_USER_ID)).thenReturn(timeout);
mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);
waitForIdle();
StrongAuthTimeoutAlarmListener alarm =
mStrongAuth.mStrongAuthTimeoutAlarmListenerForUser.get(PRIMARY_USER_ID);
// verify that a new alarm for primary auth timeout is added for the user
assertNotNull(alarm);
// verify that the alarm is scheduled
verifyAlarm(nextAlarmTime, STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm);
}
@Test
public void testReportSuccessfulStrongAuthUnlock_testRefreshStrongAuthTimeout() {
final long currentTime = 1000;
final long oldTimeout = 5000;
final long nextAlarmTime = currentTime + oldTimeout;
when(mInjector.getElapsedRealtimeMs()).thenReturn(currentTime);
when(mDPM.getRequiredStrongAuthTimeout(null, PRIMARY_USER_ID)).thenReturn(oldTimeout);
mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);
waitForIdle();
StrongAuthTimeoutAlarmListener alarm =
mStrongAuth.mStrongAuthTimeoutAlarmListenerForUser.get(PRIMARY_USER_ID);
assertEquals(currentTime, alarm.getLatestStrongAuthTime());
verifyAlarm(nextAlarmTime, STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm);
final long newTimeout = 3000;
when(mDPM.getRequiredStrongAuthTimeout(null, PRIMARY_USER_ID)).thenReturn(newTimeout);
mStrongAuth.refreshStrongAuthTimeout(PRIMARY_USER_ID);
waitForIdle();
verify(mAlarmManager).cancel(alarm);
verifyAlarm(currentTime + newTimeout, STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm);
}
@Test
public void testReportSuccessfulStrongAuthUnlock_cancelAlarmsAndAllowNonStrongBio() {
setupAlarms(PRIMARY_USER_ID);
mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);
waitForIdle();
// verify that unlocking with primary auth (PIN/pattern/password) cancels alarms
// for fallback and idle timeout and re-allow unlocking with non-strong biometric
verifyAlarmsCancelledAndNonStrongBiometricAllowed(PRIMARY_USER_ID);
}
@Test
public void testFallbackTimeout_convenienceBiometric_weakBiometric() {
// assume that unlock with convenience biometric
mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
// assume that unlock again with weak biometric
mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
waitForIdle();
// verify that the fallback alarm scheduled when unlocking with convenience biometric is
// not affected when unlocking again with weak biometric
verify(mAlarmManager, never()).cancel(any(NonStrongBiometricTimeoutAlarmListener.class));
assertNotNull(mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(PRIMARY_USER_ID));
}
private void verifyAlarm(long when, String tag, AlarmManager.OnAlarmListener alarm) {
verify(mAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME),
eq(when),
eq(tag),
eq(alarm),
eq(mStrongAuth.mHandler));
}
private void verifyStrongAuthFlags(int reason, int userId) {
final int flags = mStrongAuth.mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
Log.d(TAG, "verifyStrongAuthFlags:"
+ " reason=" + Integer.toHexString(reason)
+ " userId=" + userId
+ " flags=" + Integer.toHexString(flags));
assertTrue(containsFlag(flags, reason));
}
private void setupAlarms(int userId) {
// schedule (a) an alarm for non-strong biometric fallback timeout and (b) an alarm for
// non-strong biometric idle timeout, so later we can verify that unlocking with
// strong biometric or primary auth will cancel those alarms
mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
}
private void verifyAlarmsCancelledAndNonStrongBiometricAllowed(int userId) {
// verify that the current alarm for non-strong biometric fallback timeout is cancelled and
// removed
verify(mAlarmManager).cancel(any(NonStrongBiometricTimeoutAlarmListener.class));
assertNull(mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(userId));
// verify that the current alarm for non-strong biometric idle timeout is cancelled
verify(mAlarmManager).cancel(any(NonStrongBiometricIdleTimeoutAlarmListener.class));
// verify that unlocking with non-strong biometrics is allowed
assertTrue(mStrongAuth.mIsNonStrongBiometricAllowedForUser
.get(userId, mDefaultIsNonStrongBiometricAllowed));
}
private static boolean containsFlag(int haystack, int needle) {
return (haystack & needle) != 0;
}
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
}