Commit 0cfe5ddb authored by Vasilii Sukhanov's avatar Vasilii Sukhanov Committed by Commit Bot

Add a helper class that determines a state of the compromised credential

reminder.

The bubble appears after save/update password bubble reminding the user
about the remaining issues.

Bug: 1049200
Change-Id: I289938d66911453fce34923bf9db2b7515bc29d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2249937
Commit-Queue: Vasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: default avatarIoana Pandele <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/master@{#779845}
parent 6f44f1f5
......@@ -228,6 +228,8 @@ jumbo_static_library("browser") {
"ui/password_check_referrer.cc",
"ui/password_check_referrer.h",
"ui/plaintext_reason.h",
"ui/post_save_compromised_helper.cc",
"ui/post_save_compromised_helper.h",
"ui/saved_passwords_presenter.cc",
"ui/saved_passwords_presenter.h",
"votes_uploader.cc",
......@@ -591,6 +593,7 @@ source_set("unit_tests") {
"sync_username_test_base.h",
"ui/bulk_leak_check_service_adapter_unittest.cc",
"ui/compromised_credentials_manager_unittest.cc",
"ui/post_save_compromised_helper_unittest.cc",
"ui/saved_passwords_presenter_unittest.cc",
"vote_uploads_test_matchers.h",
"votes_uploader_unittest.cc",
......
// 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.
#include "components/password_manager/core/browser/ui/post_save_compromised_helper.h"
#include "base/stl_util.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
namespace password_manager {
// Maximum time since the last password check while the result is considered
// up to date.
constexpr auto kMaxTimeSinceLastCheck = base::TimeDelta::FromMinutes(30);
PostSaveCompromisedHelper::PostSaveCompromisedHelper(
base::span<const CompromisedCredentials> compromised,
const base::string16& current_username) {
for (const CompromisedCredentials& credential : compromised) {
if (credential.username == current_username)
current_leak_ = credential;
}
}
PostSaveCompromisedHelper::~PostSaveCompromisedHelper() = default;
void PostSaveCompromisedHelper::AnalyzeLeakedCredentials(
PasswordStore* store,
PrefService* prefs,
BubbleCallback callback) {
callback_ = std::move(callback);
prefs_ = prefs;
store->GetAllCompromisedCredentials(this);
}
void PostSaveCompromisedHelper::OnGetCompromisedCredentials(
std::vector<CompromisedCredentials> compromised_credentials) {
const bool compromised_password_changed =
current_leak_ && !base::Contains(compromised_credentials, *current_leak_);
bubble_type_ = BubbleType::kNoBubble;
if (compromised_credentials.empty()) {
if (compromised_password_changed) {
// Obtain the timestamp of the last completed check. This is 0.0 in case
// the check never completely ran before.
const double last_check_completed =
prefs_->GetDouble(prefs::kLastTimePasswordCheckCompleted);
if (last_check_completed &&
base::Time::Now() - base::Time::FromDoubleT(last_check_completed) <
kMaxTimeSinceLastCheck) {
bubble_type_ = BubbleType::kPasswordUpdatedSafeState;
}
}
} else {
bubble_type_ = compromised_password_changed
? BubbleType::kPasswordUpdatedWithMoreToFix
: BubbleType::kUnsafeState;
}
compromised_count_ = compromised_credentials.size();
std::move(callback_).Run(bubble_type_, compromised_count_);
}
} // namespace password_manager
// 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_POST_SAVE_COMPROMISED_HELPER_H_
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_POST_SAVE_COMPROMISED_HELPER_H_
#include "base/callback.h"
#include "base/containers/span.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "components/password_manager/core/browser/compromised_credentials_consumer.h"
#include "components/password_manager/core/browser/compromised_credentials_table.h"
class PrefService;
namespace password_manager {
class PasswordStore;
// Helps to choose a compromised credential bubble after a password was saved.
class PostSaveCompromisedHelper : public CompromisedCredentialsConsumer {
public:
enum class BubbleType {
// No follow-up bubble should be shown.
kNoBubble,
// Last compromised password was updated and the password check completed
// recently. The user is presumed safe.
kPasswordUpdatedSafeState,
// A compromised password was updated and there are more issues to fix.
kPasswordUpdatedWithMoreToFix,
// A random password was saved/updated. There are stored compromised
// credentials.
kUnsafeState,
};
// The callback is told which bubble to bring up and how many compromised
// credentials in total should be still fixed.
using BubbleCallback = base::OnceCallback<void(BubbleType, size_t)>;
// |compromised| contains all compromised credentials for the current site.
// |current_username| is the username that was just saved or updated.
PostSaveCompromisedHelper(
base::span<const CompromisedCredentials> compromised,
const base::string16& current_username);
~PostSaveCompromisedHelper() override;
PostSaveCompromisedHelper(const PostSaveCompromisedHelper&) = delete;
PostSaveCompromisedHelper& operator=(const PostSaveCompromisedHelper&) =
delete;
// Asynchronously queries the password store for the compromised credentials
// and notifies |callback| with the result of analysis.
void AnalyzeLeakedCredentials(PasswordStore* store,
PrefService* prefs,
BubbleCallback callback);
BubbleType bubble_type() const { return bubble_type_; }
size_t compromised_count() const { return compromised_count_; }
private:
void OnGetCompromisedCredentials(
std::vector<CompromisedCredentials> compromised_credentials) override;
// Contains the entry for the currently leaked credentials if it was leaked.
base::Optional<CompromisedCredentials> current_leak_;
// Profile prefs.
PrefService* prefs_ = nullptr;
// Callback to notify the caller about the bubble type.
BubbleCallback callback_;
// BubbleType after the callback was executed.
BubbleType bubble_type_ = BubbleType::kNoBubble;
// Count of compromised credentials after the callback was executed.
size_t compromised_count_ = 0;
};
} // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_POST_SAVE_COMPROMISED_HELPER_H_
// 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.
#include "components/password_manager/core/browser/ui/post_save_compromised_helper.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/password_manager/core/browser/mock_password_store.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace password_manager {
namespace {
using BubbleType = PostSaveCompromisedHelper::BubbleType;
using prefs::kLastTimePasswordCheckCompleted;
using testing::Return;
constexpr char kSignonRealm[] = "https://example.com/";
constexpr char kUsername[] = "user";
constexpr char kUsername2[] = "user2";
CompromisedCredentials CreateCompromised(base::StringPiece username) {
return CompromisedCredentials{
.signon_realm = kSignonRealm,
.username = base::ASCIIToUTF16(username),
.compromise_type = CompromiseType::kLeaked,
};
}
} // namespace
class PostSaveCompromisedHelperTest : public testing::Test {
public:
PostSaveCompromisedHelperTest() {
mock_store_ = new MockPasswordStore;
EXPECT_TRUE(mock_store_->Init(&prefs_));
prefs_.registry()->RegisterDoublePref(kLastTimePasswordCheckCompleted, 0.0);
}
~PostSaveCompromisedHelperTest() override {
mock_store_->ShutdownOnUIThread();
}
void WaitForPasswordStore() { task_environment_.RunUntilIdle(); }
MockPasswordStore* store() { return mock_store_.get(); }
TestingPrefServiceSimple* prefs() { return &prefs_; }
private:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
scoped_refptr<MockPasswordStore> mock_store_;
TestingPrefServiceSimple prefs_;
};
TEST_F(PostSaveCompromisedHelperTest, DefaultState) {
PostSaveCompromisedHelper helper({}, base::ASCIIToUTF16(kUsername));
EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type());
EXPECT_EQ(0u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, EmptyStore) {
PostSaveCompromisedHelper helper({}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kNoBubble, 0));
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl);
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type());
EXPECT_EQ(0u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, RandomSite_FullStore) {
PostSaveCompromisedHelper helper({}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kUnsafeState, 1));
std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername2)};
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl)
.WillOnce(Return(saved));
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kUnsafeState, helper.bubble_type());
EXPECT_EQ(1u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, CompromisedSite_ItemStayed) {
std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername),
CreateCompromised(kUsername2)};
PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kUnsafeState, 2));
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl)
.WillOnce(Return(saved));
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kUnsafeState, helper.bubble_type());
EXPECT_EQ(2u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, CompromisedSite_ItemGone) {
std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername),
CreateCompromised(kUsername2)};
PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kPasswordUpdatedWithMoreToFix, 1));
saved = {CreateCompromised(kUsername2)};
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl)
.WillOnce(Return(saved));
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kPasswordUpdatedWithMoreToFix, helper.bubble_type());
EXPECT_EQ(1u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, FixedLast_BulkCheckNeverDone) {
std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername)};
PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kNoBubble, 0));
saved = {};
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl)
.WillOnce(Return(saved));
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type());
EXPECT_EQ(0u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, FixedLast_BulkCheckDoneLongAgo) {
prefs()->SetDouble(
kLastTimePasswordCheckCompleted,
(base::Time::Now() - base::TimeDelta::FromDays(5)).ToDoubleT());
std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername)};
PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kNoBubble, 0));
saved = {};
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl)
.WillOnce(Return(saved));
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type());
EXPECT_EQ(0u, helper.compromised_count());
}
TEST_F(PostSaveCompromisedHelperTest, FixedLast_BulkCheckDoneRecently) {
prefs()->SetDouble(
kLastTimePasswordCheckCompleted,
(base::Time::Now() - base::TimeDelta::FromMinutes(1)).ToDoubleT());
std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername)};
PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername));
base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback;
EXPECT_CALL(callback, Run(BubbleType::kPasswordUpdatedSafeState, 0));
saved = {};
EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl)
.WillOnce(Return(saved));
helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get());
WaitForPasswordStore();
EXPECT_EQ(BubbleType::kPasswordUpdatedSafeState, helper.bubble_type());
EXPECT_EQ(0u, helper.compromised_count());
}
} // namespace password_manager
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