blob: 4320ec40e0a8fe791ee1f301b11c4d8831f08da7 [file] [log] [blame]
// 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.
#include "base/callback_list.h"
#include "base/test/run_until.h"
#include "chrome/browser/affiliations/affiliation_service_factory.h"
#include "chrome/browser/password_manager/chrome_password_change_service.h"
#include "chrome/browser/password_manager/password_change_delegate_impl.h"
#include "chrome/browser/password_manager/password_change_service_factory.h"
#include "chrome/browser/password_manager/password_manager_test_base.h"
#include "chrome/browser/password_manager/passwords_navigation_observer.h"
#include "chrome/browser/password_manager/profile_password_store_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h"
#include "chrome/browser/ui/passwords/bubble_controllers/password_change/password_change_info_bubble_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/affiliations/core/browser/mock_affiliation_service.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
using affiliations::AffiliationService;
using affiliations::MockAffiliationService;
namespace {
class MockPasswordChangeDelegateObserver
: public PasswordChangeDelegate::Observer {
public:
MOCK_METHOD(void,
OnStateChanged,
(PasswordChangeDelegate::State),
(override));
MOCK_METHOD(void,
OnPasswordChangeStopped,
(PasswordChangeDelegate*),
(override));
};
std::unique_ptr<KeyedService> CreateTestAffiliationService(
content::BrowserContext* context) {
return std::make_unique<testing::NiceMock<MockAffiliationService>>();
}
content::WebContents* OpenNewTabInBackground(base::WeakPtr<Browser> browser,
const GURL& url,
content::WebContents*) {
if (!browser) {
return nullptr;
}
int preexisting_tab = browser->tab_strip_model()->active_index();
content::WebContents* contents = PasswordManagerBrowserTestBase::GetNewTab(
browser.get(), /*open_new_tab=*/true);
ui_test_utils::NavigateToURLWithDisposition(
browser.get(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
browser->tab_strip_model()->ActivateTabAt(preexisting_tab);
return contents;
}
} // namespace
class PasswordChangeBrowserTest : public PasswordManagerBrowserTestBase {
public:
void SetUpInProcessBrowserTestFixture() override {
PasswordManagerBrowserTestBase::SetUpInProcessBrowserTestFixture();
create_services_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating([](content::BrowserContext* context) {
AffiliationServiceFactory::GetInstance()->SetTestingFactory(
context,
base::BindRepeating(&CreateTestAffiliationService));
}));
}
void SetUpOnMainThread() override {
PasswordManagerBrowserTestBase::SetUpOnMainThread();
}
MockAffiliationService* affiliation_service() {
return static_cast<MockAffiliationService*>(
AffiliationServiceFactory::GetForProfile(browser()->profile()));
}
ChromePasswordChangeService* password_change_service() {
return PasswordChangeServiceFactory::GetForProfile(browser()->profile());
;
}
// This allows to attach a custom ManagePasswordsUIController to intercept UI
// interactions.
void InterceptNewTabsOpening() {
// TODO(crbug.com/382652112): find a way to observe bubbles without
// intercepting new tab creation.
password_change_service()->SetCustomTabOpening(
base::BindRepeating(&OpenNewTabInBackground, browser()->AsWeakPtr()));
}
private:
base::CallbackListSubscription create_services_subscription_;
base::WeakPtrFactory<PasswordChangeBrowserTest> weak_ptr_factory_{this};
};
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
StartingPasswordChangeOpensNewTab) {
TabStripModel* tab_strip = browser()->tab_strip_model();
// Assert that there is a single tab.
ASSERT_EQ(1, tab_strip->count());
ASSERT_FALSE(
password_change_service()->GetPasswordChangeDelegate(WebContents()));
GURL main_url("https://example.com/"),
change_pwd_url("https://example.com/password/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(change_pwd_url));
password_change_service()->StartPasswordChange(main_url, u"test", u"password",
WebContents());
// Verify a new tab is added, although the focus remained on the initial tab.
ASSERT_EQ(2, tab_strip->count());
EXPECT_EQ(0, tab_strip->active_index());
// Verify a new tab is opened with a change pwd url.
EXPECT_EQ(change_pwd_url, tab_strip->GetWebContentsAt(1)->GetURL());
// Verify that GetPasswordChangeDelegate() returns delegate for both tabs.
EXPECT_TRUE(password_change_service()->GetPasswordChangeDelegate(
tab_strip->GetWebContentsAt(0)));
EXPECT_TRUE(password_change_service()->GetPasswordChangeDelegate(
tab_strip->GetWebContentsAt(1)));
EXPECT_EQ(password_change_service()->GetPasswordChangeDelegate(
tab_strip->GetWebContentsAt(0)),
password_change_service()->GetPasswordChangeDelegate(
tab_strip->GetWebContentsAt(1)));
}
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
ChangePasswordFormIsFilledAutomatically) {
GURL main_url("https://example.com/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(embedded_test_server()->GetURL(
"/password/update_form_empty_fields.html")));
password_change_service()->StartPasswordChange(main_url, u"test", u"pa$$word",
WebContents());
// Activate tab with password change to simplify testing.
SetWebContents(browser()->tab_strip_model()->GetWebContentsAt(1));
PasswordsNavigationObserver observer(WebContents());
EXPECT_TRUE(observer.Wait());
// Wait and verify the old password is filled correctly.
WaitForElementValue("password", "pa$$word");
// Verify there is a new password generated and it's filled into both fields.
std::string new_password =
GetElementValue(/*iframe_id=*/"null", "new_password_1");
EXPECT_FALSE(new_password.empty());
CheckElementValue("new_password_2", new_password);
}
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest, PasswordChangeStateUpdated) {
MockPasswordChangeDelegateObserver observer;
GURL main_url("https://example.com/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(embedded_test_server()->GetURL(
"/password/update_form_empty_fields.html")));
password_change_service()->StartPasswordChange(main_url, u"test", u"pa$$word",
WebContents());
// Verify the delegate is created and it's currently waiting for change
// password form.
auto* delegate = password_change_service()->GetPasswordChangeDelegate(
browser()->tab_strip_model()->GetWebContentsAt(0));
ASSERT_TRUE(delegate);
delegate->AddObserver(&observer);
EXPECT_EQ(PasswordChangeDelegate::State::kWaitingForChangePasswordForm,
delegate->GetCurrentState());
// Verify observer is invoked when the state changes.
EXPECT_CALL(observer,
OnStateChanged(PasswordChangeDelegate::State::kChangingPassword));
// Activate tab with password change to simplify testing.
SetWebContents(browser()->tab_strip_model()->GetWebContentsAt(1));
PasswordsNavigationObserver navigation_observer(WebContents());
EXPECT_TRUE(navigation_observer.Wait());
// Wait and verify the old password is filled correctly.
WaitForElementValue("password", "pa$$word");
EXPECT_EQ(PasswordChangeDelegate::State::kChangingPassword,
delegate->GetCurrentState());
delegate->RemoveObserver(&observer);
}
// TODO(crbug.com/382703186): Fix flakiness and re-enable.
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
DISABLED_GeneratedPasswordIsPreSaved) {
GURL main_url("https://example.com/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(embedded_test_server()->GetURL(
"/password/update_form_empty_fields.html")));
password_change_service()->StartPasswordChange(main_url, u"test", u"pa$$word",
WebContents());
// Activate tab with password change to simplify testing.
SetWebContents(browser()->tab_strip_model()->GetWebContentsAt(1));
PasswordsNavigationObserver observer(WebContents());
EXPECT_TRUE(observer.Wait());
WaitForElementValue("password", "pa$$word");
// Verify generated password is pre-saved.
WaitForPasswordStore();
CheckThatCredentialsStored(
/*username=*/"", GetElementValue(/*iframe_id=*/"null", "new_password_1"));
}
// Verify that after password change is stopped, password change delegate is not
// returned.
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest, StopPasswordChange) {
GURL main_url("https://example.com/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(
embedded_test_server()->GetURL("/password/done.html")));
password_change_service()->StartPasswordChange(main_url, u"test", u"pa$$word",
WebContents());
auto* password_change_tab = browser()->tab_strip_model()->GetWebContentsAt(1);
ASSERT_TRUE(password_change_service()->GetPasswordChangeDelegate(
password_change_tab));
password_change_service()
->GetPasswordChangeDelegate(password_change_tab)
->Stop();
EXPECT_FALSE(password_change_service()->GetPasswordChangeDelegate(
password_change_tab));
}
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest, NewPasswordIsSaved) {
GURL main_url("https://example.com/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(embedded_test_server()->GetURL(
"/password/update_form_empty_fields.html")));
password_change_service()->StartPasswordChange(main_url, u"test", u"pa$$word",
WebContents());
// Activate tab with password change to simplify testing.
SetWebContents(browser()->tab_strip_model()->GetWebContentsAt(1));
PasswordsNavigationObserver password_change_page_observer(WebContents());
EXPECT_TRUE(password_change_page_observer.Wait());
WaitForElementValue("password", "pa$$word");
std::string new_password =
GetElementValue(/*iframe_id=*/"null", "new_password_1");
// Emulate a navigation as an indication of successful submission.
PasswordsNavigationObserver new_page_observer(WebContents());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("example.com", "/password/done.html")));
EXPECT_TRUE(new_page_observer.Wait());
// Verify generated password is saved.
WaitForPasswordStore();
CheckThatCredentialsStored("test", new_password);
// Verify the success state.
PasswordChangeDelegate* delegate =
password_change_service()->GetPasswordChangeDelegate(
browser()->tab_strip_model()->GetWebContentsAt(0));
ASSERT_EQ(delegate->GetCurrentState(),
PasswordChangeDelegate::State::kPasswordSuccessfullyChanged);
}
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest, OldPasswordIsUpdated) {
password_manager::PasswordStoreInterface* password_store =
ProfilePasswordStoreFactory::GetForProfile(
browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
.get();
GURL origin = embedded_test_server()->GetURL("example.com", "/");
password_manager::PasswordForm form;
form.signon_realm = origin.spec();
form.url = origin;
form.username_value = u"test";
form.password_value = u"pa$$word";
password_store->AddLogin(form);
WaitForPasswordStore();
GURL main_url("https://example.com/");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(embedded_test_server()->GetURL(
"example.com", "/password/update_form_empty_fields.html")));
password_change_service()->StartPasswordChange(
main_url, form.username_value, form.password_value, WebContents());
// Activate tab with password change to simplify testing.
SetWebContents(browser()->tab_strip_model()->GetWebContentsAt(1));
PasswordsNavigationObserver password_change_page_observer(WebContents());
EXPECT_TRUE(password_change_page_observer.Wait());
WaitForElementValue("password", "pa$$word");
std::string new_password =
GetElementValue(/*iframe_id=*/"null", "new_password_1");
// Emulate a navigation as an indication of successful submission.
PasswordsNavigationObserver new_page_observer(WebContents());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("example.com", "/password/done.html")));
EXPECT_TRUE(new_page_observer.Wait());
// Verify saved password is updated.
WaitForPasswordStore();
CheckThatCredentialsStored(base::UTF16ToUTF8(form.username_value),
new_password);
}
IN_PROC_BROWSER_TEST_F(PasswordChangeBrowserTest,
SignInCheckBubgehbleIsHiddenWhenStateIsUpdated) {
GURL main_url("https://example.com/");
GURL change_password_url =
embedded_test_server()->GetURL("/password/update_form_empty_fields.html");
EXPECT_CALL(*affiliation_service(), GetChangePasswordURL(main_url))
.WillOnce(testing::Return(change_password_url));
password_change_service()->StartPasswordChange(main_url, u"test", u"pa$$word",
WebContents());
// Verify the delegate is created and it's currently waiting for change
// password form.
auto* delegate = password_change_service()->GetPasswordChangeDelegate(
browser()->tab_strip_model()->GetWebContentsAt(0));
ASSERT_TRUE(delegate);
PasswordBubbleViewBase::ShowBubble(
WebContents(), LocationBarBubbleDelegateView::USER_GESTURE);
auto* bubble_controller = static_cast<PasswordChangeInfoBubbleController*>(
PasswordBubbleViewBase::manage_password_bubble()->GetController());
ASSERT_EQ(
bubble_controller->GetTitle(),
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UI_SIGN_IN_CHECK_TITLE));
ASSERT_EQ(bubble_controller->GetDisplayOrigin(),
url_formatter::FormatUrlForSecurityDisplay(change_password_url));
// Wait until the state is changed from `kWaitingForChangePasswordForm` to any
// other state. The bubble should disappear then.
ASSERT_TRUE(base::test::RunUntil([&]() {
return delegate->GetCurrentState() !=
PasswordChangeDelegate::State::kWaitingForChangePasswordForm;
}));
PasswordBubbleViewBase* bubble =
PasswordBubbleViewBase::manage_password_bubble();
ASSERT_FALSE(bubble);
}