Commit 844cd90c authored by Eleonora Rocchi's avatar Eleonora Rocchi Committed by Commit Bot

[PwdCheckAndroid] Add dialog to view a compromised credential

This CL adds a dialog that shows a compromised credential. The dialog
is shown when the ¨View password¨ entry is clicked on the More menu of
a compromised credential. It also adds a test to verify the close
button of the dialog works correctly.

Screenshot in the linked bug.

Bug: 1117502, 1092444
Change-Id: Iff5784b3e78a3f58072c7e7944f85f6d93b6820a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2363750Reviewed-by: default avatarIoana Pandele <ioanap@chromium.org>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Commit-Queue: Eleonora Rocchi <erocchi@google.com>
Cr-Commit-Position: refs/heads/master@{#800107}
parent 5f2f2631
...@@ -83,10 +83,12 @@ android_library("internal_java") { ...@@ -83,10 +83,12 @@ android_library("internal_java") {
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckDeletionDialogFragment.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckDeletionDialogFragment.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckDialogFragment.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckProperties.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckProperties.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewDialogFragment.java",
"java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewHolder.java", "java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewHolder.java",
"java/src/org/chromium/chrome/browser/password_check/helper/PasswordCheckChangePasswordHelper.java", "java/src/org/chromium/chrome/browser/password_check/helper/PasswordCheckChangePasswordHelper.java",
"java/src/org/chromium/chrome/browser/password_check/helper/PasswordCheckReauthenticationHelper.java", "java/src/org/chromium/chrome/browser/password_check/helper/PasswordCheckReauthenticationHelper.java",
...@@ -110,6 +112,7 @@ android_resources("java_resources") { ...@@ -110,6 +112,7 @@ android_resources("java_resources") {
"java/res/layout/password_check_compromised_credential_item.xml", "java/res/layout/password_check_compromised_credential_item.xml",
"java/res/layout/password_check_compromised_credential_with_script_item.xml", "java/res/layout/password_check_compromised_credential_with_script_item.xml",
"java/res/layout/password_check_header_item.xml", "java/res/layout/password_check_header_item.xml",
"java/res/layout/password_check_view_credential_dialog.xml",
"java/res/values/dimens.xml", "java/res/values/dimens.xml",
] ]
create_srcjar = false create_srcjar = false
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/view_dialog_compromised_password"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_margin="@dimen/view_dialog_compromised_password_margin"
android:textAppearance="@style/TextAppearance.TextLarge.Secondary"/>
</LinearLayout>
\ No newline at end of file
...@@ -29,4 +29,6 @@ ...@@ -29,4 +29,6 @@
<dimen name="compromised_credential_row_padding_start">16dp</dimen> <dimen name="compromised_credential_row_padding_start">16dp</dimen>
<dimen name="compromised_credential_row_padding_top">12dp</dimen> <dimen name="compromised_credential_row_padding_top">12dp</dimen>
<dimen name="compromised_credential_row_reason_margin_top">2dp</dimen> <dimen name="compromised_credential_row_reason_margin_top">2dp</dimen>
<dimen name="view_dialog_compromised_password_margin">24dp</dimen>
</resources> </resources>
...@@ -5,33 +5,21 @@ ...@@ -5,33 +5,21 @@
package org.chromium.chrome.browser.password_check; package org.chromium.chrome.browser.password_check;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import org.chromium.chrome.browser.password_check.internal.R; import org.chromium.chrome.browser.password_check.internal.R;
/** /**
* Shows the dialog that confirms the user really wants to delete a credential. * Shows the dialog that confirms the user really wants to delete a credential.
*/ */
public class PasswordCheckDeletionDialogFragment extends DialogFragment { public class PasswordCheckDeletionDialogFragment extends PasswordCheckDialogFragment {
/**
* This interface combines handling the clicks on the buttons and the general dismissal of the
* dialog.
*/
interface Handler extends DialogInterface.OnClickListener {
/** Handle the dismissal of the dialog.*/
void onDismiss();
}
// This handler is used to answer the user actions on the dialog. // This handler is used to answer the user actions on the dialog.
private final Handler mHandler;
private final String mOrigin; private final String mOrigin;
PasswordCheckDeletionDialogFragment(Handler handler, String origin) { PasswordCheckDeletionDialogFragment(Handler handler, String origin) {
mHandler = handler; super(handler);
mOrigin = origin; mOrigin = origin;
} }
...@@ -51,19 +39,4 @@ public class PasswordCheckDeletionDialogFragment extends DialogFragment { ...@@ -51,19 +39,4 @@ public class PasswordCheckDeletionDialogFragment extends DialogFragment {
getString(R.string.password_check_delete_credential_dialog_body, mOrigin)) getString(R.string.password_check_delete_credential_dialog_body, mOrigin))
.create(); .create();
} }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
dismiss();
return;
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mHandler != null) mHandler.onDismiss();
}
} }
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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.password_check;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
/**
* Class to create a dialog in the Password check view.
*/
public class PasswordCheckDialogFragment extends DialogFragment {
/**
* This interface combines handling the clicks on the buttons and the general dismissal of the
* dialog.
*/
interface Handler extends DialogInterface.OnClickListener {
/** Handle the dismissal of the dialog.*/
void onDismiss();
}
// This handler is used to answer the user actions on the dialog.
protected final Handler mHandler;
PasswordCheckDialogFragment(Handler handler) {
mHandler = handler;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
dismiss();
return;
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mHandler != null) mHandler.onDismiss();
}
}
...@@ -17,6 +17,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties ...@@ -17,6 +17,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_DIALOG_HANDLER;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.util.Pair; import android.util.Pair;
...@@ -186,12 +188,23 @@ class PasswordCheckMediator ...@@ -186,12 +188,23 @@ class PasswordCheckMediator
return; return;
} }
mReauthenticationHelper.reauthenticate(ReauthReason.VIEW_PASSWORD, mReauthenticationHelper.reauthenticate(ReauthReason.VIEW_PASSWORD, reauthSucceeded -> {
reauthSucceeded if (reauthSucceeded) {
-> { mModel.set(VIEW_CREDENTIAL, credential);
// TODO(crbug.com/1117502) Add implementation to view the compromised mModel.set(VIEW_DIALOG_HANDLER, new PasswordCheckViewDialogFragment.Handler() {
// credential @Override
public void onClick(DialogInterface dialog, int which) {
mModel.set(VIEW_CREDENTIAL, null);
mModel.set(VIEW_DIALOG_HANDLER, null);
}
@Override
public void onDismiss() {
mModel.set(VIEW_DIALOG_HANDLER, null);
}
}); });
}
});
} }
@Override @Override
......
...@@ -27,8 +27,14 @@ class PasswordCheckProperties { ...@@ -27,8 +27,14 @@ class PasswordCheckProperties {
new PropertyModel.WritableObjectPropertyKey<>("deletion_confirmation_handler"); new PropertyModel.WritableObjectPropertyKey<>("deletion_confirmation_handler");
static final PropertyModel.WritableObjectPropertyKey<String> DELETION_ORIGIN = static final PropertyModel.WritableObjectPropertyKey<String> DELETION_ORIGIN =
new PropertyModel.WritableObjectPropertyKey<>("deletion_origin"); new PropertyModel.WritableObjectPropertyKey<>("deletion_origin");
static final PropertyModel.WritableObjectPropertyKey<CompromisedCredential> VIEW_CREDENTIAL =
static final PropertyKey[] ALL_KEYS = {ITEMS, DELETION_CONFIRMATION_HANDLER, DELETION_ORIGIN}; new PropertyModel.WritableObjectPropertyKey<>("view_credential");
static final PropertyModel.WritableObjectPropertyKey<PasswordCheckViewDialogFragment.Handler>
VIEW_DIALOG_HANDLER =
new PropertyModel.WritableObjectPropertyKey<>("view_dialog_handler");
static final PropertyKey[] ALL_KEYS = {ITEMS, DELETION_CONFIRMATION_HANDLER, DELETION_ORIGIN,
VIEW_CREDENTIAL, VIEW_DIALOG_HANDLER};
static PropertyModel createDefaultModel() { static PropertyModel createDefaultModel() {
return new PropertyModel.Builder(ALL_KEYS).with(ITEMS, new ListModel<>()).build(); return new PropertyModel.Builder(ALL_KEYS).with(ITEMS, new ListModel<>()).build();
......
...@@ -17,6 +17,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties ...@@ -17,6 +17,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_DIALOG_HANDLER;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
...@@ -78,6 +80,12 @@ class PasswordCheckViewBinder { ...@@ -78,6 +80,12 @@ class PasswordCheckViewBinder {
model.get(DELETION_CONFIRMATION_HANDLER), model.get(DELETION_ORIGIN))); model.get(DELETION_CONFIRMATION_HANDLER), model.get(DELETION_ORIGIN)));
} else if (propertyKey == DELETION_ORIGIN) { } else if (propertyKey == DELETION_ORIGIN) {
// Binding not necessary (only used indirectly). // Binding not necessary (only used indirectly).
} else if (propertyKey == VIEW_CREDENTIAL) {
// Binding not necessary (only used indirectly).
} else if (propertyKey == VIEW_DIALOG_HANDLER) {
if (model.get(VIEW_DIALOG_HANDLER) == null) return; // Initial or onDismiss.
view.showDialogFragment(new PasswordCheckViewDialogFragment(
model.get(VIEW_DIALOG_HANDLER), model.get(VIEW_CREDENTIAL)));
} else { } else {
assert false : "Unhandled update to property:" + propertyKey; assert false : "Unhandled update to property:" + propertyKey;
} }
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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.password_check;
import android.app.Dialog;
import android.os.Bundle;
import android.text.InputType;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.chromium.chrome.browser.password_check.internal.R;
/**
* Shows the dialog that allows the user to see the compromised credential.
*/
public class PasswordCheckViewDialogFragment extends PasswordCheckDialogFragment {
private CompromisedCredential mCredential;
PasswordCheckViewDialogFragment(Handler handler, CompromisedCredential credential) {
super(handler);
mCredential = credential;
}
/**
* Opens the dialog with the compromised credential
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View dialogContent = getActivity().getLayoutInflater().inflate(
R.layout.password_check_view_credential_dialog, null);
TextView passwordView = dialogContent.findViewById(R.id.view_dialog_compromised_password);
passwordView.setText(mCredential.getPassword());
passwordView.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
AlertDialog viewDialog = new AlertDialog.Builder(getActivity())
.setTitle(mCredential.getDisplayOrigin())
.setNegativeButton(R.string.close, mHandler)
.setView(dialogContent)
.create();
return viewDialog;
}
}
...@@ -32,6 +32,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties ...@@ -32,6 +32,8 @@ import static org.chromium.chrome.browser.password_check.PasswordCheckProperties
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.RESTART_BUTTON_ACTION;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.HeaderProperties.UNKNOWN_PROGRESS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS; import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.ITEMS;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_CREDENTIAL;
import static org.chromium.chrome.browser.password_check.PasswordCheckProperties.VIEW_DIALOG_HANDLER;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_NO_PASSWORDS; import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_NO_PASSWORDS;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_OFFLINE; import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_OFFLINE;
import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT; import static org.chromium.chrome.browser.password_check.PasswordCheckUIStatus.ERROR_QUOTA_LIMIT;
...@@ -401,6 +403,20 @@ public class PasswordCheckViewTest { ...@@ -401,6 +403,20 @@ public class PasswordCheckViewTest {
assertThat(getHeaderSubtitle().getVisibility(), is(View.GONE)); assertThat(getHeaderSubtitle().getVisibility(), is(View.GONE));
} }
@Test
@SmallTest
public void testGetTimestampStrings() {
Resources res = mPasswordCheckView.getContext().getResources();
assertThat(PasswordCheckViewBinder.getTimestamp(res, 10 * S_TO_MS), is("Just now"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, MIN_TO_MS), is("1 minute ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 17 * MIN_TO_MS), is("17 minutes ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, H_TO_MS), is("1 hour ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 13 * H_TO_MS), is("13 hours ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, DAY_TO_MS), is("1 day ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 2 * DAY_TO_MS), is("2 days ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 315 * DAY_TO_MS), is("315 days ago"));
}
@Test @Test
@MediumTest @MediumTest
public void testCredentialDisplaysNameOriginAndReason() { public void testCredentialDisplaysNameOriginAndReason() {
...@@ -561,20 +577,6 @@ public class PasswordCheckViewTest { ...@@ -561,20 +577,6 @@ public class PasswordCheckViewTest {
verify(mMockHandler).onView(eq(ANA)); verify(mMockHandler).onView(eq(ANA));
} }
@Test
@SmallTest
public void testGetTimestampStrings() {
Resources res = mPasswordCheckView.getContext().getResources();
assertThat(PasswordCheckViewBinder.getTimestamp(res, 10 * S_TO_MS), is("Just now"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, MIN_TO_MS), is("1 minute ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 17 * MIN_TO_MS), is("17 minutes ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, H_TO_MS), is("1 hour ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 13 * H_TO_MS), is("13 hours ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, DAY_TO_MS), is("1 day ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 2 * DAY_TO_MS), is("2 days ago"));
assertThat(PasswordCheckViewBinder.getTimestamp(res, 315 * DAY_TO_MS), is("315 days ago"));
}
@Test @Test
@MediumTest @MediumTest
public void testConfirmingDeletionDialogTriggersHandler() { public void testConfirmingDeletionDialogTriggersHandler() {
...@@ -599,6 +601,30 @@ public class PasswordCheckViewTest { ...@@ -599,6 +601,30 @@ public class PasswordCheckViewTest {
assertThat(recordedConfirmation.get(), is(1)); assertThat(recordedConfirmation.get(), is(1));
} }
@Test
@MediumTest
public void testCloseViewDialogTriggersHandler() {
final AtomicInteger recordedClosure = new AtomicInteger(0);
PasswordCheckDeletionDialogFragment.Handler fakeHandler =
new PasswordCheckDeletionDialogFragment.Handler() {
@Override
public void onDismiss() {}
@Override
public void onClick(DialogInterface dialogInterface, int i) {
recordedClosure.incrementAndGet();
}
};
mModel.set(VIEW_CREDENTIAL, ANA);
runOnUiThreadBlocking(() -> mModel.set(VIEW_DIALOG_HANDLER, fakeHandler));
onView(withText(R.string.close))
.inRoot(withDecorView(
not(is(mPasswordCheckView.getActivity().getWindow().getDecorView()))))
.perform(click());
assertThat(recordedClosure.get(), is(1));
}
private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status, private MVCListAdapter.ListItem buildHeader(@PasswordCheckUIStatus int status,
Integer compromisedCredentialsCount, Long checkTimestamp) { Integer compromisedCredentialsCount, Long checkTimestamp) {
return buildHeader(status, compromisedCredentialsCount, checkTimestamp, null); return buildHeader(status, compromisedCredentialsCount, checkTimestamp, null);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment