diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 4c4795cbd84..0bf1255b3b9 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -33,6 +33,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_C import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW; import android.app.Activity; import android.app.Dialog; @@ -795,6 +796,9 @@ public class ChooseLockGeneric extends SettingsActivity { if (getIntent().getBooleanExtra(EXTRA_SHOW_OPTIONS_BUTTON, false)) { intent.putExtra(EXTRA_SHOW_OPTIONS_BUTTON, chooseLockSkipped); } + if (getIntent().getBooleanExtra(EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false)) { + intent.putExtra(EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, true); + } intent.putExtra(EXTRA_CHOOSE_LOCK_GENERIC_EXTRAS, getIntent().getExtras()); // If the caller requested Gatekeeper Password Handle to be returned, we assume it // came from biometric enrollment. onActivityResult will put the LockSettingsService diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 6d5ce905cff..f8f4345f893 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -232,6 +232,7 @@ public class ChooseLockPassword extends SettingsActivity { private LockscreenCredential mCurrentCredential; private LockscreenCredential mChosenPassword; private boolean mRequestGatekeeperPassword; + private boolean mRequestWriteRepairModePassword; private ImeAwareEditText mPasswordEntry; private TextViewInputDisabler mPasswordEntryInputDisabler; @@ -559,6 +560,8 @@ public class ChooseLockPassword extends SettingsActivity { ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); mRequestGatekeeperPassword = intent.getBooleanExtra( ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); + mRequestWriteRepairModePassword = intent.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false); if (savedInstanceState == null) { updateStage(Stage.Introduction); if (confirmCredentials) { @@ -568,6 +571,7 @@ public class ChooseLockPassword extends SettingsActivity { .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) .setReturnCredentials(true) .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword) .setUserId(mUserId) .show(); } @@ -1009,7 +1013,8 @@ public class ChooseLockPassword extends SettingsActivity { mSaveAndFinishWorker = new SaveAndFinishWorker(); mSaveAndFinishWorker .setListener(this) - .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword); + .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword); getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, FRAGMENT_TAG_SAVE_AND_FINISH).commit(); diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index e309a606abd..7569c1596ec 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -204,6 +204,7 @@ public class ChooseLockPattern extends SettingsActivity { private LockscreenCredential mCurrentCredential; private boolean mRequestGatekeeperPassword; + private boolean mRequestWriteRepairModePassword; protected TextView mHeaderText; protected LockPatternView mLockPatternView; protected TextView mFooterText; @@ -561,6 +562,8 @@ public class ChooseLockPattern extends SettingsActivity { intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); mRequestGatekeeperPassword = intent.getBooleanExtra( ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); + mRequestWriteRepairModePassword = intent.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false); if (savedInstanceState == null) { if (confirmCredentials) { @@ -574,6 +577,7 @@ public class ChooseLockPattern extends SettingsActivity { .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) .setReturnCredentials(true) .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword) .setUserId(mUserId) .show(); @@ -827,7 +831,8 @@ public class ChooseLockPattern extends SettingsActivity { mSaveAndFinishWorker = new SaveAndFinishWorker(); mSaveAndFinishWorker .setListener(this) - .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword); + .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) + .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword); getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, FRAGMENT_TAG_SAVE_AND_FINISH).commit(); diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index 9533314c8a2..e5fc5507815 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -73,6 +73,8 @@ public final class ChooseLockSettingsHelper { public static final String EXTRA_KEY_GK_PW_HANDLE = "gk_pw_handle"; public static final String EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW = "request_write_repair_mode_pw"; + public static final String EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL = + "wrote_repair_mode_credential"; /** * When EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL and EXTRA_KEY_UNIFICATION_PROFILE_ID are diff --git a/src/com/android/settings/password/SaveAndFinishWorker.java b/src/com/android/settings/password/SaveAndFinishWorker.java index 1af3b15d312..df679e5f6cd 100644 --- a/src/com/android/settings/password/SaveAndFinishWorker.java +++ b/src/com/android/settings/password/SaveAndFinishWorker.java @@ -24,6 +24,7 @@ import android.util.Log; import android.util.Pair; import android.widget.Toast; +import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import com.android.internal.widget.LockPatternUtils; @@ -45,6 +46,7 @@ public class SaveAndFinishWorker extends Fragment { private LockPatternUtils mUtils; private boolean mRequestGatekeeperPassword; + private boolean mRequestWriteRepairModePassword; private boolean mWasSecureBefore; private int mUserId; private int mUnificationProfileId = UserHandle.USER_NULL; @@ -72,7 +74,8 @@ public class SaveAndFinishWorker extends Fragment { return this; } - public void start(LockPatternUtils utils, LockscreenCredential chosenCredential, + @VisibleForTesting + void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential, LockscreenCredential currentCredential, int userId) { mUtils = utils; mUserId = userId; @@ -84,7 +87,11 @@ public class SaveAndFinishWorker extends Fragment { mChosenCredential = chosenCredential; mCurrentCredential = currentCredential != null ? currentCredential : LockscreenCredential.createNone(); + } + public void start(LockPatternUtils utils, LockscreenCredential chosenCredential, + LockscreenCredential currentCredential, int userId) { + prepare(utils, chosenCredential, currentCredential, userId); if (mBlocking) { finish(saveAndVerifyInBackground().second); } else { @@ -97,31 +104,49 @@ public class SaveAndFinishWorker extends Fragment { * @return pair where the first is a boolean confirming whether the change was successful or not * and second is the Intent which has the challenge token or is null. */ - private Pair saveAndVerifyInBackground() { + @VisibleForTesting + Pair saveAndVerifyInBackground() { final int userId = mUserId; - final boolean success = mUtils.setLockCredential(mChosenCredential, mCurrentCredential, - userId); - if (success) { - unifyProfileCredentialIfRequested(); + if (!mUtils.setLockCredential(mChosenCredential, mCurrentCredential, userId)) { + return Pair.create(false, null); } - Intent result = null; - if (success && mRequestGatekeeperPassword) { + + unifyProfileCredentialIfRequested(); + + @LockPatternUtils.VerifyFlag int flags = 0; + if (mRequestGatekeeperPassword) { // If a Gatekeeper Password was requested, invoke the LockSettingsService code // path to return a Gatekeeper Password based on the credential that the user // chose. This should only be run if the credential was successfully set. - final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenCredential, - userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE); - - if (!response.isMatched() || !response.containsGatekeeperPasswordHandle()) { - Log.e(TAG, "critical: bad response or missing GK PW handle for known good" - + " credential: " + response.toString()); - } - - result = new Intent(); - result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, - response.getGatekeeperPasswordHandle()); + flags |= LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE; } - return Pair.create(success, result); + if (mRequestWriteRepairModePassword) { + flags |= LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + } + if (flags == 0) { + return Pair.create(true, null); + } + + Intent result = new Intent(); + final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenCredential, + userId, flags); + if (response.isMatched()) { + if (mRequestGatekeeperPassword && response.containsGatekeeperPasswordHandle()) { + result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + response.getGatekeeperPasswordHandle()); + } else if (mRequestGatekeeperPassword) { + Log.e(TAG, "critical: missing GK PW handle for known good credential: " + response); + } + } else { + Log.e(TAG, "critical: bad response for known good credential: " + response); + } + if (mRequestWriteRepairModePassword) { + // Notify the caller if repair mode credential is saved successfully + result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, + response.isMatched()); + } + + return Pair.create(true, result); } private void finish(Intent resultData) { @@ -141,6 +166,11 @@ public class SaveAndFinishWorker extends Fragment { return this; } + public SaveAndFinishWorker setRequestWriteRepairModePassword(boolean value) { + mRequestWriteRepairModePassword = value; + return this; + } + public SaveAndFinishWorker setBlocking(boolean blocking) { mBlocking = blocking; return this; diff --git a/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java b/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java new file mode 100644 index 00000000000..88e31508155 --- /dev/null +++ b/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 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.settings.password; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.internal.widget.VerifyCredentialResponse; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SaveAndFinishWorkerTest { + @Test + public void testSetRequestWriteRepairModePassword_setLockCredentialFail() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(false); + + worker.setRequestWriteRepairModePassword(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu, never()).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isFalse(); + } + + @Test + public void testSetRequestWriteRepairModePassword_verifyCredentialFail() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + var response = VerifyCredentialResponse.fromError(); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true); + when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response); + + worker.setRequestWriteRepairModePassword(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isTrue(); + assertThat(result.second.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, true)) + .isFalse(); + } + + @Test + public void testSetRequestWriteRepairModePassword_verifyCredentialSucceed() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + var response = new VerifyCredentialResponse.Builder().build(); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true); + when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response); + + worker.setRequestWriteRepairModePassword(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isTrue(); + assertThat(result.second.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, false)) + .isTrue(); + } + + @Test + public void testSetRequestWriteRepairModePassword_verifyCredentialSucceed_noGkPwHandle() { + int userId = 0; + int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW + | LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE; + var chosenCredential = LockscreenCredential.createPassword("1234"); + var currentCredential = LockscreenCredential.createNone(); + var worker = new SaveAndFinishWorker(); + var lpu = mock(LockPatternUtils.class); + var response = new VerifyCredentialResponse.Builder().build(); + + when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true); + when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response); + + worker.setRequestWriteRepairModePassword(true); + worker.setRequestGatekeeperPasswordHandle(true); + worker.prepare(lpu, chosenCredential, currentCredential, userId); + var result = worker.saveAndVerifyInBackground(); + + verify(lpu).setLockCredential(chosenCredential, currentCredential, userId); + verify(lpu).verifyCredential(chosenCredential, userId, flags); + assertThat(result.first).isTrue(); + assertThat(result.second.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, false)) + .isTrue(); + assertThat(result.second.getLongExtra( + ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, -1)) + .isEqualTo(-1); + } +}