blob: 1e005c10a409d6c307f2e0bae035ed782ad8b323 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/extensions/api/identity/web_auth_flow_info_bar_delegate.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/nuke_profile_directory_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
class MockWebAuthFlowDelegate : public WebAuthFlow::Delegate {
public:
MOCK_METHOD(void, OnAuthFlowURLChange, (const GURL&), (override));
MOCK_METHOD(void, OnAuthFlowTitleChange, (const std::string&), (override));
MOCK_METHOD(void, OnAuthFlowFailure, (WebAuthFlow::Failure), (override));
};
class WebAuthFlowBrowserTest : public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
// Delete the flow early if OnAuthFlowFailure is called. Simulates real
// usages.
ON_CALL(mock(), OnAuthFlowFailure(testing::_))
.WillByDefault(
[this](WebAuthFlow::Failure failure) { DeleteWebAuthFlow(); });
}
void DeleteWebAuthFlow() {
DCHECK(web_auth_flow_);
// Delete the web auth flow (uses DeleteSoon).
web_auth_flow_.release()->DetachDelegateAndDelete();
}
void TearDownOnMainThread() override {
// Ensures any timer tasks finish.
timeout_task_runner_->RunUntilIdle();
if (web_auth_flow_) {
DeleteWebAuthFlow();
}
// Ensures `web_auth_flow_` is deleted before teardown. This cannot be run
// in |DeleteWebAuthFlow| as it can be called from `timeout_task_runner_`'s
// loop.
base::RunLoop().RunUntilIdle();
InProcessBrowserTest::TearDownOnMainThread();
}
void StartWebAuthFlow(
const GURL& url,
WebAuthFlow::Mode mode = WebAuthFlow::Mode::INTERACTIVE,
Profile* profile = nullptr,
WebAuthFlow::AbortOnLoad abort_on_load_for_non_interactive =
WebAuthFlow::AbortOnLoad::kYes,
std::optional<base::TimeDelta> timeout_for_non_interactive = std::nullopt,
std::optional<gfx::Rect> popup_bounds = std::nullopt) {
if (!profile)
profile = GetProfile();
web_auth_flow_ = std::make_unique<WebAuthFlow>(
&mock_web_auth_flow_delegate_, profile, url, mode,
/*user_gesture=*/true, abort_on_load_for_non_interactive,
timeout_for_non_interactive, popup_bounds);
timeout_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
web_auth_flow_->SetClockForTesting(timeout_task_runner_->GetMockTickClock(),
timeout_task_runner_);
web_auth_flow_->Start();
}
WebAuthFlow* web_auth_flow() { return web_auth_flow_.get(); }
content::WebContents* web_contents() {
if (!web_auth_flow_) {
return nullptr;
}
return web_auth_flow_->web_contents();
}
MockWebAuthFlowDelegate& mock() { return mock_web_auth_flow_delegate_; }
scoped_refptr<base::TestMockTimeTaskRunner> timeout_task_runner() {
return timeout_task_runner_;
}
private:
std::unique_ptr<WebAuthFlow> web_auth_flow_;
MockWebAuthFlowDelegate mock_web_auth_flow_delegate_;
scoped_refptr<base::TestMockTimeTaskRunner> timeout_task_runner_;
};
class WebAuthFlowInBrowserTabParamBrowserTest : public WebAuthFlowBrowserTest {
public:
bool JsRedirectToUrl(const GURL& url) {
content::TestNavigationObserver redirect_observer(url);
redirect_observer.WatchExistingWebContents();
const std::string script =
base::StringPrintf("window.location.href = '%s'", url.spec().c_str());
bool result = content::ExecJs(web_contents(), script);
if (result) {
redirect_observer.Wait();
}
return result;
}
};
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowURLChangeCalled) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
// Observer for waiting until a navigation to a url has finished.
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url);
navigation_observer.WaitForNavigationFinished();
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowFailureChangeCalled) {
// Navigate to a url that doesn't exist.
const GURL error_url = embedded_test_server()->GetURL("/error");
content::TestNavigationObserver navigation_observer(error_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowFailure should be called
// by DidFinishNavigation.
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::LOAD_FAILED));
StartWebAuthFlow(error_url);
navigation_observer.WaitForNavigationFinished();
}
// Tests that the flow launched in silent mode with default parameters will
// terminate immediately with the "interacation required" error if the page
// loads and does not navigate to the redirect URL.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowFailureCalledInteractionRequired) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// In SILENT mode, DidStopLoading() will force the auth flow to fail if it has
// not already redirected, because we did not specify a timeout.
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
StartWebAuthFlow(auth_url, WebAuthFlow::SILENT);
navigation_observer.Wait();
}
// Tests that the flow launched in silent mode with
// `abortOnLoadForNonInteractive` set to `false` will terminate with the
// "interaction required" after a specified timeout if the page loads and does
// not navigate to the redirect URL.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowInteractionRequiredWithTimeout) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
// before calling OnAuthFlowFailure.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
WebAuthFlow::AbortOnLoad::kNo,
/*timeout_for_non_interactive=*/base::Milliseconds(50));
navigation_observer.Wait();
testing::Mock::VerifyAndClearExpectations(&mock());
// Increment the time by 40ms - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Milliseconds(40));
testing::Mock::VerifyAndClearExpectations(&mock());
// Now we exceed our 50ms limit and expect a failure.
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
timeout_task_runner()->FastForwardBy(base::Milliseconds(20));
}
// Tests that the flow launched in silent mode with
// `abortOnLoadForNonInteractive` set to `false` will terminate with the
// "interaction required" error after a default timeout if the page loads and
// does not navigate to the redirect URL.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowInteractionRequiredWithDefaultTimeout) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// In SILENT mode, DidStopLoading() will wait for the default 1 minute timeout
// before calling OnAuthFlowFailure.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
WebAuthFlow::AbortOnLoad::kNo);
navigation_observer.Wait();
testing::Mock::VerifyAndClearExpectations(&mock());
// Increment the time by 59s - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Seconds(59));
testing::Mock::VerifyAndClearExpectations(&mock());
// Now we exceed the 1 minute default limit and expect a failure.
// The error is "interaction required" when the flow times out when the page
// has already loaded.
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
timeout_task_runner()->FastForwardBy(base::Seconds(2));
}
// Tests that the flow launched in silent mode with `timeoutMsForNonInteractive`
// set will terminate with the "timed out" error after a timeout if the page
// fails to load (distinct from the flow failing to navigate to the redirect URL
// in time).
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowPageLoadTimeout) {
const GURL auth_url = embedded_test_server()->GetURL("/hung-after-headers");
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
base::RunLoop run_loop;
ON_CALL(mock(), OnAuthFlowURLChange(testing::_))
.WillByDefault([&run_loop](const GURL& url) { run_loop.Quit(); });
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
// before calling OnAuthFlowFailure.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
WebAuthFlow::AbortOnLoad::kYes,
/*timeout_for_non_interactive=*/base::Milliseconds(50));
// Wait for navigation to the failing page to start first.
run_loop.Run();
testing::Mock::VerifyAndClearExpectations(&mock());
// Increment the time by 40ms - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Milliseconds(40));
testing::Mock::VerifyAndClearExpectations(&mock());
// Now we exceed our 50ms limit and expect a failure. The error is "load
// timed out" when the flow times out while the page is still loading.
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::TIMED_OUT));
timeout_task_runner()->FastForwardBy(base::Milliseconds(20));
}
// Tests that the flow launched in silent mode with
// `abortOnLoadForNonInteractive` set to `false` and
// `timeoutMsForNonInteractive` set will succeed if it navigates to the redirect
// URL before the timeout.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowRedirectBeforeTimeout) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
// before calling OnAuthFlowFailure.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
WebAuthFlow::AbortOnLoad::kNo,
/*timeout_for_non_interactive=*/base::Milliseconds(50));
navigation_observer.Wait();
testing::Mock::VerifyAndClearExpectations(&mock());
// Increment the time by 40ms - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Milliseconds(40));
testing::Mock::VerifyAndClearExpectations(&mock());
// Redirect after page load and check we get OnAuthFlowURLChange.
const GURL redirect_url = embedded_test_server()->GetURL("/title2.html");
EXPECT_CALL(mock(), OnAuthFlowURLChange(redirect_url));
EXPECT_TRUE(JsRedirectToUrl(redirect_url));
}
// Tests that the loaded auth page can redirect multiple times and fails only
// after the timeout.
IN_PROC_BROWSER_TEST_F(WebAuthFlowInBrowserTabParamBrowserTest,
OnAuthFlowMultipleRedirects) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
// The delegate method OnAuthFlowURLChange should be called
// by DidStartNavigation.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// In SILENT mode, DidStopLoading() will wait for our specified 50ms timeout
// before calling OnAuthFlowFailure.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
StartWebAuthFlow(auth_url, WebAuthFlow::SILENT, /*profile=*/nullptr,
WebAuthFlow::AbortOnLoad::kNo,
/*timeout_for_non_interactive=*/base::Milliseconds(50));
navigation_observer.Wait();
testing::Mock::VerifyAndClearExpectations(&mock());
// Increment the time by 10ms - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Milliseconds(10));
testing::Mock::VerifyAndClearExpectations(&mock());
// Redirect after page load and check we get OnAuthFlowURLChange.
const GURL redirect_url = embedded_test_server()->GetURL("/title2.html");
EXPECT_CALL(mock(), OnAuthFlowURLChange(redirect_url));
EXPECT_TRUE(JsRedirectToUrl(redirect_url));
// Increment the time by 10ms - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Milliseconds(10));
testing::Mock::VerifyAndClearExpectations(&mock());
// Redirect after 2nd page load and check we get OnAuthFlowURLChange.
const GURL redirect_url2 = embedded_test_server()->GetURL("/title3.html");
EXPECT_CALL(mock(), OnAuthFlowURLChange(redirect_url2));
EXPECT_TRUE(JsRedirectToUrl(redirect_url2));
testing::Mock::VerifyAndClearExpectations(&mock());
// Increment the time by 10ms - nothing should happen.
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
timeout_task_runner()->FastForwardBy(base::Milliseconds(10));
testing::Mock::VerifyAndClearExpectations(&mock());
// Now we exceed our 50ms limit and expect a failure.
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::INTERACTION_REQUIRED));
timeout_task_runner()->FastForwardBy(base::Milliseconds(30));
}
class WebAuthFlowFencedFrameTest
: public WebAuthFlowInBrowserTabParamBrowserTest {
public:
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}
private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
};
IN_PROC_BROWSER_TEST_F(WebAuthFlowFencedFrameTest,
FencedFrameNavigationSuccess) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
// Observer for waiting until loading stops. A fenced frame will be created
// after load has finished.
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.set_wait_event(
content::TestNavigationObserver::WaitEvent::kLoadStopped);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url);
navigation_observer.Wait();
testing::Mock::VerifyAndClearExpectations(&mock());
// Navigation for fenced frames should not affect to call the delegate methods
// in the WebAuthFlow.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url)).Times(0);
// Create a fenced frame into the inner WebContents of the WebAuthFlow.
ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetPrimaryMainFrame(),
embedded_test_server()->GetURL("/fenced_frames/title1.html")));
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowFencedFrameTest,
FencedFrameNavigationFailure) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
// Observer for waiting until loading stops. A fenced frame will be created
// after load has finished.
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.set_wait_event(
content::TestNavigationObserver::WaitEvent::kLoadStopped);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url);
navigation_observer.Wait();
testing::Mock::VerifyAndClearExpectations(&mock());
// Navigation for fenced frames should not affect to call the delegate methods
// in the WebAuthFlow.
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url)).Times(0);
EXPECT_CALL(mock(), OnAuthFlowFailure).Times(0);
// Create a fenced frame into the inner WebContents of the WebAuthFlow.
ASSERT_TRUE(fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetPrimaryMainFrame(),
embedded_test_server()->GetURL("/error"), net::Error::ERR_FAILED));
}
// This test is in two parts:
// - First create a WebAuthFlow in interactive mode that will create a new tab
// with the auth_url.
// - Close the new created tab, simulating the user declining the consent by
// closing the tab.
//
// These two tests are combined into one in order not to re-test the tab
// creation twice.
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
InteractivePopupWindowCreatedWithAuthURL_ThenCloseTab) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
const char extension_name[] = "extension_name";
web_auth_flow()->SetShouldShowInfoBar(extension_name);
navigation_observer.Wait();
Browser* popup_browser = chrome::FindBrowserWithTab(web_contents());
EXPECT_EQ(popup_browser->type(), Browser::Type::TYPE_POPUP);
EXPECT_NE(browser(), popup_browser);
TabStripModel* tabs = popup_browser->tab_strip_model();
EXPECT_EQ(tabs->GetActiveWebContents()->GetLastCommittedURL(), auth_url);
// Check info bar exists and displays proper message with extension name.
base::WeakPtr<WebAuthFlowInfoBarDelegate> infobar_delegate =
web_auth_flow()->GetInfoBarDelegateForTesting();
EXPECT_TRUE(infobar_delegate);
EXPECT_EQ(
infobar_delegate->GetIdentifier(),
infobars::InfoBarDelegate::EXTENSIONS_WEB_AUTH_FLOW_INFOBAR_DELEGATE);
EXPECT_TRUE(infobar_delegate->GetMessageText().find(
base::UTF8ToUTF16(std::string(extension_name))));
//---------------------------------------------------------------------
// Part of the test that closes the tab, simulating declining the consent.
//---------------------------------------------------------------------
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::Failure::WINDOW_CLOSED));
tabs->CloseWebContentsAt(tabs->active_index(), 0);
}
IN_PROC_BROWSER_TEST_F(
WebAuthFlowBrowserTest,
InteractivePopupWindowCreatedWithAuthURL_NavigationInURLDoesNotBreakTheFlow) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
web_auth_flow()->SetShouldShowInfoBar("extension name");
navigation_observer.Wait();
//---------------------------------------------------------------------
// Browser-initiated URL change in the opened tab before completing the auth
// flow should not trigger an auth flow failure in popup window mode
// specifically to allow Back/Forward navigation. Other types of URL changes
// such as new URL input are actually disabled in the Popup window by the UI.
//---------------------------------------------------------------------
testing::Mock::VerifyAndClearExpectations(&mock());
// Keeping a reference to the info bar delegate to check later.
base::WeakPtr<WebAuthFlowInfoBarDelegate> auth_info_bar =
web_auth_flow()->GetInfoBarDelegateForTesting();
ASSERT_TRUE(auth_info_bar);
Browser* popup_browser = chrome::FindBrowserWithTab(web_contents());
EXPECT_EQ(popup_browser->type(), Browser::Type::TYPE_POPUP);
EXPECT_NE(browser(), popup_browser);
// Simulate an internal navigation, such as an authentication that needs an
// input of username and password on two different pages/urls.
GURL new_url = embedded_test_server()->GetURL("/title2.html");
EXPECT_CALL(mock(), OnAuthFlowURLChange(new_url));
ASSERT_TRUE(content::NavigateToURL(web_contents(), new_url));
EXPECT_EQ(web_contents()->GetURL(), new_url);
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// TODO(crbug.com/40272465): Need to disable BackForwardCaching as it
// causes crashes since the WebContent is initially loaded in an
// unattached mode.
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
ASSERT_TRUE(content::HistoryGoBack(web_contents()));
EXPECT_EQ(web_contents()->GetURL(), auth_url);
// Popup window is still active.
EXPECT_TRUE(popup_browser);
EXPECT_EQ(chrome::FindBrowserWithTab(web_contents()), popup_browser);
// Infobar should not be closed on navigation.
EXPECT_TRUE(auth_info_bar);
}
IN_PROC_BROWSER_TEST_F(
WebAuthFlowBrowserTest,
InteractiveNoBrowser_WebAuthCreatesBrowserWithPopupWindow) {
Profile* profile = GetProfile();
// Simulates an extension being opened, in order for the profile not to be
// added for destruction.
ScopedProfileKeepAlive profile_keep_alive(
profile, ProfileKeepAliveOrigin::kBackgroundMode);
ScopedKeepAlive keep_alive{KeepAliveOrigin::BROWSER,
KeepAliveRestartOption::DISABLED};
CloseBrowserSynchronously(browser());
ASSERT_FALSE(chrome::FindBrowserWithProfile(profile));
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE, profile);
navigation_observer.Wait();
Browser* new_browser = chrome::FindBrowserWithProfile(profile);
EXPECT_TRUE(new_browser);
EXPECT_EQ(new_browser->type(), Browser::Type::TYPE_POPUP);
EXPECT_EQ(new_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL(),
auth_url);
}
// This is a regression test for crbug/1445824, makes sure the opened popup
// window does not trigger Session restore.
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
InteractiveNoBrowser_NotActivatingSessionRestore) {
Profile* profile = GetProfile();
// Enable SessionRestore to last used pages.
SessionStartupPref startup_pref(SessionStartupPref::LAST);
SessionStartupPref::SetStartupPref(profile, startup_pref);
// Simulates an extension being opened, with no active browser.
ScopedProfileKeepAlive profile_keep_alive(
profile, ProfileKeepAliveOrigin::kBackgroundMode);
ScopedKeepAlive keep_alive{KeepAliveOrigin::BROWSER,
KeepAliveRestartOption::DISABLED};
CloseBrowserSynchronously(browser());
ASSERT_FALSE(chrome::FindBrowserWithProfile(profile));
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE, profile);
navigation_observer.Wait();
// Makes sure only one browser is created and profile is not trying to restore
// previous tabs.
EXPECT_FALSE(SessionRestore::IsRestoring(profile));
EXPECT_EQ(chrome::FindAllBrowsersWithProfile(profile).size(), 1u);
Browser* new_browser = chrome::FindBrowserWithProfile(profile);
EXPECT_TRUE(new_browser);
EXPECT_EQ(new_browser->type(), Browser::Type::TYPE_POPUP);
EXPECT_EQ(new_browser->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL(),
auth_url);
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, SilentNewTabNotCreated) {
TabStripModel* tabs = browser()->tab_strip_model();
int initial_tab_count = tabs->count();
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(),
OnAuthFlowFailure(WebAuthFlow::Failure::INTERACTION_REQUIRED));
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::SILENT);
navigation_observer.Wait();
// Tab not created, tab count did not increase.
EXPECT_EQ(tabs->count(), initial_tab_count);
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
InteractiveNewTabCreatedWithAuthURL_NoInfoBarByDefault) {
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
navigation_observer.Wait();
Browser* popup_browser = chrome::FindBrowserWithTab(web_contents());
TabStripModel* tabs = popup_browser->tab_strip_model();
EXPECT_NE(browser(), popup_browser);
EXPECT_EQ(tabs->GetActiveWebContents()->GetLastCommittedURL(), auth_url);
// Check info bar is not created if not set via
// `SetShouldShowInfoBar())`.
base::WeakPtr<WebAuthFlowInfoBarDelegate> infobar_delegate =
web_auth_flow()->GetInfoBarDelegateForTesting();
EXPECT_FALSE(infobar_delegate);
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
PopupWindowOpened_ThenCloseWindow) {
size_t initial_browser_count = chrome::GetTotalBrowserCount();
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
navigation_observer.Wait();
// New popup window is a browser, browser count should increment by 1.
EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count + 1);
// Retrieve the browser used in the WebAuthFlow, the popup window.
Browser* popup_window_browser = chrome::FindBrowserWithTab(web_contents());
EXPECT_NE(popup_window_browser, browser());
TabStripModel* popup_tabs = popup_window_browser->tab_strip_model();
EXPECT_EQ(popup_tabs->count(), 1);
EXPECT_EQ(popup_tabs->GetActiveWebContents()->GetLastCommittedURL(),
auth_url);
//---------------------------------------------------------------------
// Closing the browser popup window, simulating declining the consent.
//---------------------------------------------------------------------
EXPECT_CALL(mock(), OnAuthFlowFailure(WebAuthFlow::Failure::WINDOW_CLOSED));
CloseBrowserSynchronously(popup_window_browser);
}
IN_PROC_BROWSER_TEST_F(
WebAuthFlowBrowserTest,
Interactive_MarkedForDeletionProfileNotAllowedToCreatePopupWindow) {
// Marking active profile for deletion.
MarkProfileDirectoryForDeletion(GetProfile()->GetPath());
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
// Profiles marked for deletion are not allowed to create a popup window and
// should return an error.
EXPECT_CALL(mock(),
OnAuthFlowFailure(WebAuthFlow::Failure::CANNOT_CREATE_WINDOW));
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
navigation_observer.Wait();
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest, PopupWindowOpened_WithBounds) {
size_t initial_browser_count = chrome::GetTotalBrowserCount();
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
EXPECT_CALL(mock(), OnAuthFlowURLChange(auth_url));
const gfx::Rect test_bounds(35, 47, 400, 400);
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE, nullptr,
WebAuthFlow::AbortOnLoad::kYes, std::nullopt, test_bounds);
navigation_observer.Wait();
// New popup window is a browser, browser count should increment by 1.
EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count + 1);
// Retrieve the browser used in the WebAuthFlow, the popup window.
Browser* popup_window_browser = chrome::FindBrowserWithTab(web_contents());
EXPECT_NE(popup_window_browser, browser());
gfx::Rect bounds = popup_window_browser->window()->GetBounds();
EXPECT_EQ(bounds.x(), test_bounds.x());
EXPECT_EQ(bounds.y(), test_bounds.y());
// The final width and height can contain platform-specific offsets for the
// window title bar, which we don't want to assert exactly here.
EXPECT_GE(bounds.width(), test_bounds.width());
EXPECT_GE(bounds.height(), test_bounds.height());
}
IN_PROC_BROWSER_TEST_F(WebAuthFlowBrowserTest,
WebContentsDestroyedBeforeProfileShutDown) {
// Default mock implementation of OnAuthFlowFailure deletes the flow. We do
// not want that behavior in this test because we want to verify WebAuthFlow's
// internal state before destruction.
ON_CALL(mock(), OnAuthFlowFailure)
.WillByDefault([](WebAuthFlow::Failure failure) {});
size_t initial_browser_count = chrome::GetTotalBrowserCount();
// Start a WebAuthFlow that will create a popup window.
const GURL auth_url = embedded_test_server()->GetURL("/title1.html");
content::TestNavigationObserver navigation_observer(auth_url);
navigation_observer.StartWatchingNewWebContents();
StartWebAuthFlow(auth_url, WebAuthFlow::Mode::INTERACTIVE);
navigation_observer.Wait();
// Authentication flow should have created a popup window.
EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count + 1);
Browser* popup = chrome::FindBrowserWithTab(web_contents());
// Simulate profile destruction notification and wait for the auth popup to
// close.
static_cast<ProfileObserver*>(web_auth_flow())
->OnProfileWillBeDestroyed(GetProfile());
ui_test_utils::WaitForBrowserToClose(popup);
// Verify that WebAuthFlow closed the WebContents.
EXPECT_TRUE(web_auth_flow());
EXPECT_FALSE(web_auth_flow()->web_contents());
EXPECT_EQ(chrome::GetTotalBrowserCount(), initial_browser_count);
}
} // namespace extensions