Skip to content

Commit

Permalink
[UPMLocalSettings] Introduce ChromeNativePasswordCheckController
Browse files Browse the repository at this point in the history
Introduces the implementation of PasswordyCheckController, which would run password check using chrome native (non-GMS core) password check.

Bug: b/312930046
Change-Id: Ibddc96b24652fad58cd1db69d8d1bb2c439af669
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5134817
Commit-Queue: Anna Tsvirchkova <atsvirchkova@google.com>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Rainhard Findling <rainhard@chromium.org>
Reviewed-by: Ioana Pandele <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1244013}
  • Loading branch information
Anna Tsvirchkova authored and Chromium LUCI CQ committed Jan 8, 2024
1 parent 1113821 commit 0090b38
Show file tree
Hide file tree
Showing 6 changed files with 342 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,49 @@ import("//third_party/jni_zero/jni_zero.gni")

android_library("java") {
sources = [
"java/src/org/chromium/chrome/browser/pwd_check_wrapper/ChromeNativePasswordCheckController.java",
"java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckController.java",
"java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckController.java",
"java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckNativeException.java",
]

deps = [
"//base:base_java",
"//build/android:build_java",
"//chrome/browser/password_check:public_java",
"//chrome/browser/password_check/android:password_check_java_enums",
"//chrome/browser/password_manager/android:java",
"//components/browser_ui/settings/android:java",
"//components/password_manager/core/browser:password_manager_java_enums",
"//components/signin/public/android:java",
"//components/sync/android:sync_java",
"//third_party/androidx:androidx_annotation_annotation_java",
]

resources_package = "org.chromium.chrome.browser.add_username_dialog"
}

robolectric_library("junit") {
testonly = true
sources = [ "java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java" ]
sources = [
"java/src/org/chromium/chrome/browser/pwd_check_wrapper/ChromeNativePasswordCheckControllerTest.java",
"java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java",
]

deps = [
":java",
"//base:base_java",
"//base:base_java_test_support",
"//base:base_junit_test_support",
"//base/test:test_support_java",
"//chrome/browser/password_check:public_java",
"//chrome/browser/password_check/android:password_check_java_enums",
"//chrome/browser/password_manager/android:java",
"//chrome/browser/password_manager/android:settings_interface_java",
"//chrome/browser/password_manager/android:test_support_java",
"//chrome/browser/profiles/android:java",
"//chrome/browser/sync/android:java",
"//components/browser_ui/settings/android:java",
"//components/prefs/android:java",
"//components/signin/public/android:java",
"//components/sync/android:sync_java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.pwd_check_wrapper;

import org.chromium.chrome.browser.password_check.PasswordCheck;
import org.chromium.chrome.browser.password_check.PasswordCheckFactory;
import org.chromium.chrome.browser.password_check.PasswordCheckUIStatus;
import org.chromium.components.browser_ui.settings.SettingsLauncher;

import java.util.concurrent.CompletableFuture;

class ChromeNativePasswordCheckController
implements PasswordCheckController, PasswordCheck.Observer {
private CompletableFuture<Integer> mPasswordsTotalCount;
private CompletableFuture<PasswordCheckResult> mPasswordCheckResult;
private final PasswordCheck mPasswordCheck;

public ChromeNativePasswordCheckController(SettingsLauncher settingsLauncher) {
mPasswordCheck = PasswordCheckFactory.getOrCreate(settingsLauncher);
}

@Override
public CompletableFuture<PasswordCheckResult> checkPasswords(
@PasswordStoreType int passwordStoreType) {
mPasswordCheckResult = new CompletableFuture<>();
mPasswordsTotalCount = new CompletableFuture<>();
// Start observing the password check events (including data loads).
mPasswordCheck.addObserver(this, false);
mPasswordCheck.startCheck();
return mPasswordCheckResult;
}

@Override
public CompletableFuture<PasswordCheckResult> getBreachedCredentialsCount(
int passwordStoreType) {
mPasswordCheckResult = new CompletableFuture<>();
mPasswordsTotalCount = new CompletableFuture<>();
mPasswordCheck.addObserver(this, true);
return mPasswordCheckResult;
}

// PasswordCheck.Observer implementation.
@Override
public void onCompromisedCredentialsFetchCompleted() {
mPasswordsTotalCount.thenAccept(
totalCount -> {
int breachedCount = mPasswordCheck.getCompromisedCredentialsCount();
mPasswordCheckResult.complete(
new PasswordCheckResult(totalCount, breachedCount));
});
}

@Override
public void onSavedPasswordsFetchCompleted() {
int totalCount = mPasswordCheck.getSavedPasswordsCount();
mPasswordsTotalCount.complete(totalCount);
}

@Override
public void onPasswordCheckStatusChanged(@PasswordCheckUIStatus int status) {
if (status == PasswordCheckUIStatus.RUNNING) {
return;
}

// Handle error state.
if (status != PasswordCheckUIStatus.IDLE) {
PasswordCheckNativeException error =
new PasswordCheckNativeException(
"Password check finished with the error " + status + ".", status);
mPasswordCheckResult.complete(new PasswordCheckResult(error));

mPasswordCheck.removeObserver(this);
}
}

/** Not relevant for this controller. */
@Override
public void onPasswordCheckProgressChanged(int alreadyProcessed, int remainingInQueue) {}
// End of PasswordCheck.Observer implementation.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.pwd_check_wrapper;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.password_check.PasswordCheck;
import org.chromium.chrome.browser.password_check.PasswordCheckFactory;
import org.chromium.chrome.browser.password_check.PasswordCheckUIStatus;
import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordCheckResult;
import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordStoreType;
import org.chromium.components.browser_ui.settings.SettingsLauncher;

import java.util.OptionalInt;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/** Unit tests for {@link ChromeNativePasswordCheckController}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ChromeNativePasswordCheckControllerTest {
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

@Mock private SettingsLauncher mSettingsLauncher;
@Mock private PasswordCheck mPasswordCheck;
private ChromeNativePasswordCheckController mController;

@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
PasswordCheckFactory.setPasswordCheckForTesting(mPasswordCheck);
mController = new ChromeNativePasswordCheckController(mSettingsLauncher);
}

/**
* The flow: checkPasswords is called -> as a result of password check 0 breached credentials
* are obtained -> passwords loading has finished.
*/
@Test
public void passwordCheckReturnsNoBreachedPasswords()
throws ExecutionException, InterruptedException {
// Set fake to return 0 breached credentials.
when(mPasswordCheck.getSavedPasswordsCount()).thenReturn(10);
when(mPasswordCheck.getCompromisedCredentialsCount()).thenReturn(0);

CompletableFuture<PasswordCheckResult> passwordCheckResultFuture =
mController.checkPasswords(PasswordStoreType.PROFILE_STORE);
verify(mPasswordCheck).startCheck();

mController.onCompromisedCredentialsFetchCompleted();
mController.onSavedPasswordsFetchCompleted();

PasswordCheckResult passwordCheckResult = passwordCheckResultFuture.get();
assertEquals(OptionalInt.of(0), passwordCheckResult.getBreachedCount());
assertEquals(OptionalInt.of(10), passwordCheckResult.getTotalPasswordsCount());
assertEquals(null, passwordCheckResult.getError());
}

/**
* The flow: passwords loading has finished and there are 0 passwords -> as a result of password
* check 0 breached credentials are obtained.
*/
@Test
public void passwordCheckReturnsNoAnyPasswords()
throws ExecutionException, InterruptedException {
// Set fake to return 0 breached credentials.
when(mPasswordCheck.getSavedPasswordsCount()).thenReturn(0);
when(mPasswordCheck.getCompromisedCredentialsCount()).thenReturn(0);

CompletableFuture<PasswordCheckResult> passwordCheckResultFuture =
mController.checkPasswords(PasswordStoreType.PROFILE_STORE);
verify(mPasswordCheck).startCheck();

mController.onSavedPasswordsFetchCompleted();
mController.onCompromisedCredentialsFetchCompleted();
mController.onPasswordCheckStatusChanged(PasswordCheckUIStatus.ERROR_NO_PASSWORDS);

PasswordCheckResult passwordCheckResult = passwordCheckResultFuture.get();
assertEquals(OptionalInt.of(0), passwordCheckResult.getBreachedCount());
assertEquals(OptionalInt.of(0), passwordCheckResult.getTotalPasswordsCount());
assertEquals(null, passwordCheckResult.getError());
}

/**
* The flow: passwords loading has finished -> checkPasswords is called -> obtained 1 breached
* credential.
*/
@Test
public void passwordCheckReturnsBreachedPassword()
throws ExecutionException, InterruptedException {
// Set fake to return 1 breached credential.
final int breachedCount = 1;
when(mPasswordCheck.getCompromisedCredentialsCount()).thenReturn(breachedCount);
when(mPasswordCheck.getSavedPasswordsCount()).thenReturn(10);

CompletableFuture<PasswordCheckResult> passwordCheckResultFuture =
mController.checkPasswords(PasswordStoreType.PROFILE_STORE);
verify(mPasswordCheck).startCheck();

mController.onCompromisedCredentialsFetchCompleted();
mController.onSavedPasswordsFetchCompleted();

PasswordCheckResult passwordCheckResult = passwordCheckResultFuture.get();
assertEquals(OptionalInt.of(breachedCount), passwordCheckResult.getBreachedCount());
assertEquals(OptionalInt.of(10), passwordCheckResult.getTotalPasswordsCount());
assertEquals(null, passwordCheckResult.getError());
}

/** The flow: passwords loading has finished -> checkPasswords returns an error state. */
@Test
public void passwordCheckReturnsOfflineError() throws ExecutionException, InterruptedException {
CompletableFuture<PasswordCheckResult> passwordCheckResultFuture =
mController.checkPasswords(PasswordStoreType.PROFILE_STORE);
verify(mPasswordCheck).startCheck();

mController.onPasswordCheckStatusChanged(PasswordCheckUIStatus.ERROR_OFFLINE);

PasswordCheckResult passwordCheckResult = passwordCheckResultFuture.get();
assertEquals(OptionalInt.empty(), passwordCheckResult.getBreachedCount());
assertEquals(OptionalInt.empty(), passwordCheckResult.getTotalPasswordsCount());
assertEquals(
PasswordCheckUIStatus.ERROR_OFFLINE,
((PasswordCheckNativeException) passwordCheckResult.getError()).errorCode);
}

/**
* The flow: getBreachedCredentialsCount is called -> breached credentials fetch completed
* giving 1 breached credential -> then all passwords fetch is completed.
*/
@Test
public void getBreachedCredentialsCountTest() throws ExecutionException, InterruptedException {
// Set fake to return 1 breached credential.
final int breachedCount = 1;
when(mPasswordCheck.getCompromisedCredentialsCount()).thenReturn(breachedCount);
when(mPasswordCheck.getSavedPasswordsCount()).thenReturn(10);

CompletableFuture<PasswordCheckResult> passwordCheckResultFuture =
mController.getBreachedCredentialsCount(PasswordStoreType.PROFILE_STORE);
verify(mPasswordCheck).addObserver(mController, true);

mController.onCompromisedCredentialsFetchCompleted();
mController.onSavedPasswordsFetchCompleted();

PasswordCheckResult passwordCheckResult = passwordCheckResultFuture.get();
assertEquals(OptionalInt.of(breachedCount), passwordCheckResult.getBreachedCount());
assertEquals(OptionalInt.of(10), passwordCheckResult.getTotalPasswordsCount());
assertEquals(null, passwordCheckResult.getError());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.pwd_check_wrapper;

import org.chromium.chrome.browser.password_check.PasswordCheckUIStatus;

/**
* The exception returned by {@link ChromeNativePasswordCheckController} notifying there was an
* error during password check.
*/
public class PasswordCheckNativeException extends Exception {
public @PasswordCheckUIStatus int errorCode;

public PasswordCheckNativeException(String message, @PasswordCheckUIStatus int status) {
super(message);
errorCode = status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError;
import org.chromium.chrome.browser.password_manager.PasswordCheckupClientHelper.PasswordCheckBackendException;
import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordCheckResult;
import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckNativeException;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
Expand Down Expand Up @@ -85,7 +86,10 @@ class PasswordsCheckPreferenceProperties {
== CredentialManagerError.BACKEND_VERSION_NOT_SUPPORTED) {
return PasswordsState.BACKEND_VERSION_NOT_SUPPORTED;
}
// TODO (b/312930046): Add logic to support the chrome native password check errors.
if (error instanceof PasswordCheckNativeException) {
return passwordsStatefromErrorState(
((PasswordCheckNativeException) error).errorCode);
}
return PasswordsState.ERROR;
}

Expand Down

0 comments on commit 0090b38

Please sign in to comment.