| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_key.h" |
| #include "chrome/browser/supervised_user/supervised_user_navigation_observer.h" |
| #include "chrome/browser/supervised_user/supervised_user_service_factory.h" |
| #include "chrome/browser/supervised_user/supervised_user_settings_service_factory.h" |
| #include "chrome/browser/supervised_user/supervised_user_test_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/test/base/mixin_based_in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chrome/test/supervised_user/supervision_mixin.h" |
| #include "components/supervised_user/core/browser/permission_request_creator_mock.h" |
| #include "components/supervised_user/core/browser/supervised_user_interstitial.h" |
| #include "components/supervised_user/core/browser/supervised_user_service.h" |
| #include "components/supervised_user/core/browser/supervised_user_settings_service.h" |
| #include "components/supervised_user/core/browser/supervised_user_url_filter.h" |
| #include "components/supervised_user/core/browser/supervised_user_utils.h" |
| #include "components/supervised_user/core/common/features.h" |
| #include "components/supervised_user/core/common/supervised_user_constants.h" |
| #include "components/supervised_user/test_support/kids_management_api_server_mock.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_paths.h" |
| #include "content/public/common/page_type.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/prerender_test_util.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/shell.h" |
| #include "chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/test/event_generator.h" |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| namespace { |
| |
| using content::NavigationController; |
| using content::WebContents; |
| |
| static const char* kExampleHost = "www.example.com"; |
| static const char* kExampleHost2 = "www.example2.com"; |
| static const char* kStrippedExampleHost = "example.com"; |
| static const char* kFamiliesHost = "families.google.com"; |
| static const char* kIframeHost1 = "www.iframe1.com"; |
| static const char* kIframeHost2 = "www.iframe2.com"; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| constexpr char kLocalUrlAccessCommand[] = "requestUrlAccessLocal"; |
| #endif |
| constexpr char kRemoteUrlAccessCommand[] = "requestUrlAccessRemote"; |
| |
| // Class to keep track of iframes created and destroyed. |
| // TODO(crbug.com/40173416): Can be replaced with |
| // WebContents::UnsafeFindFrameByFrameTreeNodeId. |
| class RenderFrameTracker : public content::WebContentsObserver { |
| public: |
| explicit RenderFrameTracker(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) {} |
| ~RenderFrameTracker() override = default; |
| |
| // content::WebContentsObserver: |
| void RenderFrameHostChanged(content::RenderFrameHost* old_host, |
| content::RenderFrameHost* new_host) override; |
| void FrameDeleted(content::FrameTreeNodeId frame_tree_node_id) override; |
| |
| content::RenderFrameHost* GetHost(content::FrameTreeNodeId frame_id) { |
| if (!base::Contains(render_frame_hosts_, frame_id)) { |
| return nullptr; |
| } |
| return render_frame_hosts_[frame_id]; |
| } |
| |
| private: |
| std::map<content::FrameTreeNodeId, |
| raw_ptr<content::RenderFrameHost, CtnExperimental>> |
| |
| render_frame_hosts_; |
| }; |
| |
| void RenderFrameTracker::RenderFrameHostChanged( |
| content::RenderFrameHost* old_host, |
| content::RenderFrameHost* new_host) { |
| render_frame_hosts_[new_host->GetFrameTreeNodeId()] = new_host; |
| } |
| |
| void RenderFrameTracker::FrameDeleted( |
| content::FrameTreeNodeId frame_tree_node_id) { |
| if (!base::Contains(render_frame_hosts_, frame_tree_node_id)) { |
| return; |
| } |
| |
| render_frame_hosts_.erase(frame_tree_node_id); |
| } |
| |
| // Helper class to wait for a particular navigation in a particular render |
| // frame in tests. |
| class NavigationFinishedWaiter : public content::WebContentsObserver { |
| public: |
| NavigationFinishedWaiter(content::WebContents* web_contents, |
| content::FrameTreeNodeId frame_id, |
| const GURL& url); |
| NavigationFinishedWaiter(const NavigationFinishedWaiter&) = delete; |
| NavigationFinishedWaiter& operator=(const NavigationFinishedWaiter&) = delete; |
| ~NavigationFinishedWaiter() override = default; |
| |
| void Wait(); |
| |
| // content::WebContentsObserver: |
| void DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) override; |
| |
| private: |
| content::FrameTreeNodeId frame_id_; |
| GURL url_; |
| bool did_finish_ = false; |
| base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed}; |
| }; |
| |
| NavigationFinishedWaiter::NavigationFinishedWaiter( |
| content::WebContents* web_contents, |
| content::FrameTreeNodeId frame_id, |
| const GURL& url) |
| : content::WebContentsObserver(web_contents), |
| frame_id_(frame_id), |
| url_(url) {} |
| |
| void NavigationFinishedWaiter::Wait() { |
| if (did_finish_) { |
| return; |
| } |
| run_loop_.Run(); |
| } |
| |
| void NavigationFinishedWaiter::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->HasCommitted()) { |
| return; |
| } |
| |
| if (navigation_handle->GetFrameTreeNodeId() != frame_id_ || |
| navigation_handle->GetURL() != url_) { |
| return; |
| } |
| |
| did_finish_ = true; |
| run_loop_.Quit(); |
| } |
| |
| class SupervisedUserNavigationThrottleTestBase |
| : public MixinBasedInProcessBrowserTest { |
| protected: |
| explicit SupervisedUserNavigationThrottleTestBase( |
| supervised_user::SupervisionMixin::SignInMode sign_in_mode) |
| : supervision_mixin_(mixin_host_, |
| this, |
| embedded_test_server(), |
| { |
| .sign_in_mode = sign_in_mode, |
| .embedded_test_server_options = |
| {.resolver_rules_map_host_list = |
| "*.example.com, *.example2.com, " |
| "example.com, example2.com, " |
| "*.families.google.com, " |
| "*.iframe1.com, *.iframe2.com, " |
| "iframe1.com, iframe2.com"}, |
| }), |
| prerender_helper_(base::BindRepeating( |
| &SupervisedUserNavigationThrottleTestBase::web_contents, |
| base::Unretained(this))) {} |
| ~SupervisedUserNavigationThrottleTestBase() override = default; |
| |
| void SetUp() override; |
| void SetUpOnMainThread() override; |
| |
| void BlockHost(const std::string& host) { |
| supervised_user_test_util::SetManualFilterForHost(browser()->profile(), |
| host, |
| /*allowlist=*/false); |
| } |
| |
| void AllowlistHost(const std::string& host) { |
| supervised_user_test_util::SetManualFilterForHost(browser()->profile(), |
| host, /*allowlist=*/true); |
| } |
| |
| bool IsInterstitialBeingShownInMainFrame(Browser* browser); |
| |
| content::test::PrerenderTestHelper& prerender_helper() { |
| return prerender_helper_; |
| } |
| |
| content::WebContents* web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| supervised_user::KidsManagementApiServerMock& kids_management_api_mock() { |
| return supervision_mixin_.api_mock_setup_mixin().api_mock(); |
| } |
| |
| private: |
| supervised_user::SupervisionMixin supervision_mixin_; |
| content::test::PrerenderTestHelper prerender_helper_; |
| }; |
| |
| bool SupervisedUserNavigationThrottleTestBase:: |
| IsInterstitialBeingShownInMainFrame(Browser* browser) { |
| WebContents* tab = browser->tab_strip_model()->GetActiveWebContents(); |
| std::u16string title; |
| ui_test_utils::GetCurrentTabTitle(browser, &title); |
| return tab->GetController().GetLastCommittedEntry()->GetPageType() == |
| content::PAGE_TYPE_ERROR && |
| title == u"Site blocked"; |
| } |
| |
| void SupervisedUserNavigationThrottleTestBase::SetUp() { |
| prerender_helper_.RegisterServerRequestMonitor(embedded_test_server()); |
| MixinBasedInProcessBrowserTest::SetUp(); |
| } |
| |
| void SupervisedUserNavigationThrottleTestBase::SetUpOnMainThread() { |
| MixinBasedInProcessBrowserTest::SetUpOnMainThread(); |
| |
| ASSERT_TRUE(embedded_test_server()->Started()); |
| } |
| |
| class SupervisedUserNavigationThrottleTest |
| : public SupervisedUserNavigationThrottleTestBase { |
| protected: |
| SupervisedUserNavigationThrottleTest() |
| : SupervisedUserNavigationThrottleTestBase( |
| supervised_user::SupervisionMixin::SignInMode::kSupervised) {} |
| ~SupervisedUserNavigationThrottleTest() override = default; |
| }; |
| |
| class SupervisedUserNavigationThrottleWithPrerenderingTest |
| : public SupervisedUserNavigationThrottleTestBase, |
| public testing::WithParamInterface<std::string> { |
| protected: |
| SupervisedUserNavigationThrottleWithPrerenderingTest() |
| : SupervisedUserNavigationThrottleTestBase( |
| supervised_user::SupervisionMixin::SignInMode::kSupervised) {} |
| ~SupervisedUserNavigationThrottleWithPrerenderingTest() override = default; |
| |
| static std::string GetTargetHint() { return GetParam(); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| SupervisedUserNavigationThrottleWithPrerenderingTest, |
| testing::Values("_self", "_blank"), |
| [](const testing::TestParamInfo<std::string>& info) { |
| return base::StrCat({"WithTargetHint", info.param}); |
| }); |
| |
| // Tests that prerendering fails in supervised user mode. |
| #if BUILDFLAG(IS_CHROMEOS) |
| // TODO(crbug.com/40201321): Flaky on ChromeOS. |
| #define MAYBE_DisallowPrerendering DISABLED_DisallowPrerendering |
| #else |
| #define MAYBE_DisallowPrerendering DisallowPrerendering |
| #endif |
| IN_PROC_BROWSER_TEST_P(SupervisedUserNavigationThrottleWithPrerenderingTest, |
| MAYBE_DisallowPrerendering) { |
| const GURL initial_url = embedded_test_server()->GetURL("/simple.html"); |
| const GURL allowed_url = |
| embedded_test_server()->GetURL("/supervised_user/simple.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| prerender_helper().NavigatePrimaryPage(initial_url); |
| |
| // If throttled, the prerendered navigation should not have started and we |
| // should not be requesting corresponding resources. |
| content::test::PrerenderHostCreationWaiter host_creation_waiter; |
| prerender_helper().AddPrerendersAsync( |
| {allowed_url}, /*eagerness=*/std::nullopt, GetTargetHint()); |
| content::FrameTreeNodeId host_id = host_creation_waiter.Wait(); |
| auto* prerender_web_contents = |
| content::WebContents::FromFrameTreeNodeId(host_id); |
| content::test::PrerenderHostObserver host_observer(*prerender_web_contents, |
| host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(0, prerender_helper().GetRequestCount(allowed_url)); |
| |
| // Regular navigation should proceed, however. |
| content::TestNavigationObserver observer( |
| web_contents(), content::MessageLoopRunner::QuitMode::IMMEDIATE, |
| /*ignore_uncommitted_navigations*/ false); |
| prerender_helper().NavigatePrimaryPage(allowed_url); |
| observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| EXPECT_EQ(allowed_url, observer.last_navigation_url()); |
| EXPECT_EQ(1, prerender_helper().GetRequestCount(allowed_url)); |
| } |
| |
| // Tests that navigating to a blocked page simply fails if there is no |
| // SupervisedUserNavigationObserver. |
| IN_PROC_BROWSER_TEST_F(SupervisedUserNavigationThrottleTest, |
| NoNavigationObserverBlock) { |
| Profile* profile = browser()->profile(); |
| supervised_user_test_util::SetWebFilterType( |
| profile, supervised_user::WebFilterType::kCertainSites); |
| |
| std::unique_ptr<WebContents> web_contents( |
| WebContents::Create(WebContents::CreateParams(profile))); |
| NavigationController& controller = web_contents->GetController(); |
| content::TestNavigationObserver observer(web_contents.get()); |
| controller.LoadURL(GURL("http://www.example.com"), content::Referrer(), |
| ui::PAGE_TRANSITION_TYPED, std::string()); |
| observer.Wait(); |
| content::NavigationEntry* entry = controller.GetVisibleEntry(); |
| ASSERT_TRUE(entry); |
| EXPECT_EQ(content::PAGE_TYPE_NORMAL, entry->GetPageType()); |
| EXPECT_FALSE(observer.last_navigation_succeeded()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserNavigationThrottleTest, |
| BlockMainFrameWithInterstitial) { |
| BlockHost(kExampleHost2); |
| |
| GURL allowed_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), allowed_url)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost2, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserNavigationThrottleTest, |
| DontBlockSubFrame) { |
| BlockHost(kExampleHost2); |
| BlockHost(kIframeHost2); |
| |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| // Both iframes (from allowed host iframe1.com as well as from blocked host |
| // iframe2.com) should be loaded normally, since we don't filter iframes |
| // (yet) - see crbug.com/651115. |
| EXPECT_TRUE(content::EvalJs(tab, "loaded1()").ExtractBool()); |
| EXPECT_TRUE(content::EvalJs(tab, "loaded2()").ExtractBool()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserNavigationThrottleTest, |
| AllowFamiliesDotGoogleDotComAccess) { |
| // Simulate families.google.com being set in the blocklist. |
| BlockHost(kFamiliesHost); |
| |
| // A production endpoint is used here because a Tast test would be too |
| // expensive to be used for this specific case. |
| const GURL kFamiliesDotGoogleDotComUrl = |
| GURL("https://families.google.com/families"); |
| |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), kFamiliesDotGoogleDotComUrl)); |
| |
| // Get the top level WebContents. |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| EXPECT_EQ(contents->GetLastCommittedURL(), kFamiliesDotGoogleDotComUrl); |
| |
| // families.google.com should not be blocked. |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| } |
| |
| class SupervisedUserIframeFilterTest |
| : public SupervisedUserNavigationThrottleTestBase { |
| protected: |
| SupervisedUserIframeFilterTest() |
| : SupervisedUserNavigationThrottleTestBase( |
| supervised_user::SupervisionMixin::SignInMode::kSupervised) { |
| } |
| |
| ~SupervisedUserIframeFilterTest() override = default; |
| |
| void SetUpOnMainThread() override; |
| void TearDownOnMainThread() override; |
| |
| std::vector<content::FrameTreeNodeId> GetBlockedFrames(); |
| const GURL& GetBlockedFrameURL(content::FrameTreeNodeId frame_id); |
| bool IsInterstitialBeingShownInFrame(content::FrameTreeNodeId frame_id); |
| bool IsRemoteApprovalsButtonBeingShown(content::FrameTreeNodeId frame_id); |
| bool IsLocalApprovalsButtonBeingShown(content::FrameTreeNodeId frame_id); |
| bool IsBlockReasonBeingShown(content::FrameTreeNodeId frame_id); |
| bool IsDetailsLinkBeingShown(content::FrameTreeNodeId frame_id); |
| void CheckPreferredApprovalButton(content::FrameTreeNodeId frame_id); |
| bool IsLocalApprovalsInsteadButtonBeingShown( |
| content::FrameTreeNodeId frame_id); |
| void SendCommandToFrame(const std::string& command_name, |
| content::FrameTreeNodeId frame_id); |
| void WaitForNavigationFinished(content::FrameTreeNodeId frame_id, |
| const GURL& url); |
| bool IsLocalWebApprovalsEnabled() const; |
| |
| supervised_user::PermissionRequestCreatorMock* permission_creator() { |
| return permission_creator_; |
| } |
| |
| RenderFrameTracker* tracker() { return tracker_.get(); } |
| |
| private: |
| bool RunCommandAndGetBooleanFromFrame(content::FrameTreeNodeId frame_id, |
| const std::string& command); |
| |
| std::unique_ptr<RenderFrameTracker> tracker_; |
| raw_ptr<supervised_user::PermissionRequestCreatorMock, DanglingUntriaged> |
| permission_creator_; |
| }; |
| |
| void SupervisedUserIframeFilterTest::SetUpOnMainThread() { |
| SupervisedUserNavigationThrottleTestBase::SetUpOnMainThread(); |
| |
| supervised_user::SupervisedUserService* service = |
| SupervisedUserServiceFactory::GetForProfile(browser()->profile()); |
| supervised_user::SupervisedUserSettingsService* settings_service = |
| SupervisedUserSettingsServiceFactory::GetForKey( |
| browser()->profile()->GetProfileKey()); |
| CHECK(settings_service); |
| std::unique_ptr<supervised_user::PermissionRequestCreator> creator = |
| std::make_unique<supervised_user::PermissionRequestCreatorMock>( |
| *settings_service); |
| permission_creator_ = |
| static_cast<supervised_user::PermissionRequestCreatorMock*>( |
| creator.get()); |
| permission_creator_->SetEnabled(); |
| service->remote_web_approvals_manager().ClearApprovalRequestsCreators(); |
| service->remote_web_approvals_manager().AddApprovalRequestCreator( |
| std::move(creator)); |
| |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| tracker_ = std::make_unique<RenderFrameTracker>(tab); |
| } |
| |
| void SupervisedUserIframeFilterTest::TearDownOnMainThread() { |
| tracker_.reset(); |
| SupervisedUserNavigationThrottleTestBase::TearDownOnMainThread(); |
| } |
| |
| std::vector<content::FrameTreeNodeId> |
| SupervisedUserIframeFilterTest::GetBlockedFrames() { |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* navigation_observer = |
| SupervisedUserNavigationObserver::FromWebContents(tab); |
| const auto& interstitials = navigation_observer->interstitials_for_test(); |
| |
| std::vector<content::FrameTreeNodeId> blocked_frames; |
| blocked_frames.reserve(interstitials.size()); |
| |
| for (const auto& elem : interstitials) { |
| blocked_frames.push_back(elem.first); |
| } |
| |
| return blocked_frames; |
| } |
| |
| const GURL& SupervisedUserIframeFilterTest::GetBlockedFrameURL( |
| content::FrameTreeNodeId frame_id) { |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* navigation_observer = |
| SupervisedUserNavigationObserver::FromWebContents(tab); |
| const auto& interstitials = navigation_observer->interstitials_for_test(); |
| DCHECK(base::Contains(interstitials, frame_id)); |
| return interstitials.at(frame_id)->url(); |
| } |
| |
| bool SupervisedUserIframeFilterTest::IsInterstitialBeingShownInFrame( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "document.querySelector('.supervised-user-block') != null"; |
| return RunCommandAndGetBooleanFromFrame(frame_id, command); |
| } |
| |
| bool SupervisedUserIframeFilterTest::IsBlockReasonBeingShown( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "getComputedStyle(document.getElementById('block-reason')).display !== " |
| "\"none\""; |
| return RunCommandAndGetBooleanFromFrame(frame_id, command); |
| } |
| |
| bool SupervisedUserIframeFilterTest::IsDetailsLinkBeingShown( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "getComputedStyle(document.getElementById('block-reason-show-details-" |
| "link')).display !== \"none\""; |
| return RunCommandAndGetBooleanFromFrame(frame_id, command); |
| } |
| |
| bool SupervisedUserIframeFilterTest::IsRemoteApprovalsButtonBeingShown( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "!document.getElementById('remote-approvals-button').hidden"; |
| return RunCommandAndGetBooleanFromFrame(frame_id, command); |
| } |
| |
| bool SupervisedUserIframeFilterTest::IsLocalApprovalsButtonBeingShown( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "!document.getElementById('local-approvals-button').hidden"; |
| return RunCommandAndGetBooleanFromFrame(frame_id, command); |
| } |
| |
| void SupervisedUserIframeFilterTest::CheckPreferredApprovalButton( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "document.getElementById('local-approvals-button').classList.contains(" |
| "'primary-button') &&" |
| " !document.getElementById('local-approvals-button').classList." |
| "contains('secondary-button') &&" |
| " document.getElementById('remote-approvals-button').classList." |
| "contains('secondary-button') &&" |
| " !document.getElementById('remote-approvals-button').classList." |
| "contains('primary-button');"; |
| ASSERT_TRUE(RunCommandAndGetBooleanFromFrame(frame_id, command)); |
| } |
| |
| bool SupervisedUserIframeFilterTest::IsLocalApprovalsInsteadButtonBeingShown( |
| content::FrameTreeNodeId frame_id) { |
| std::string command = |
| "!document.getElementById('local-approvals-remote-request-sent-button')." |
| "hidden"; |
| return RunCommandAndGetBooleanFromFrame(frame_id, command); |
| } |
| |
| void SupervisedUserIframeFilterTest::SendCommandToFrame( |
| const std::string& command_name, |
| content::FrameTreeNodeId frame_id) { |
| auto* render_frame_host = tracker()->GetHost(frame_id); |
| DCHECK(render_frame_host); |
| DCHECK(render_frame_host->IsRenderFrameLive()); |
| std::string command = base::StrCat({"sendCommand(\'", command_name, "\')"}); |
| ASSERT_TRUE( |
| content::ExecJs(content::ToRenderFrameHost(render_frame_host), command)); |
| } |
| |
| void SupervisedUserIframeFilterTest::WaitForNavigationFinished( |
| content::FrameTreeNodeId frame_id, |
| const GURL& url) { |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| NavigationFinishedWaiter waiter(tab, frame_id, url); |
| waiter.Wait(); |
| } |
| |
| bool SupervisedUserIframeFilterTest::RunCommandAndGetBooleanFromFrame( |
| content::FrameTreeNodeId frame_id, |
| const std::string& command) { |
| // First check that SupervisedUserNavigationObserver believes that there is |
| // an error page in the frame with frame tree node id |frame_id|. |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| auto* navigation_observer = |
| SupervisedUserNavigationObserver::FromWebContents(tab); |
| auto& interstitials = navigation_observer->interstitials_for_test(); |
| |
| if (!base::Contains(interstitials, frame_id)) { |
| return false; |
| } |
| |
| auto* render_frame_host = tracker()->GetHost(frame_id); |
| DCHECK(render_frame_host->IsRenderFrameLive()); |
| |
| auto target = content::ToRenderFrameHost(render_frame_host); |
| return content::EvalJs(target, command, |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE) |
| .ExtractBool(); |
| } |
| |
| // Returns whether the feature is in fact enabled, rather that was requested to |
| // be enabled. |
| bool SupervisedUserIframeFilterTest::IsLocalWebApprovalsEnabled() const { |
| return supervised_user::IsLocalWebApprovalsEnabled(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, BlockSubFrame) { |
| base::HistogramTester histogram_tester; |
| |
| BlockHost(kIframeHost2); |
| GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| // The first iframe's source is |kIframeHost1| and it is not blocked. It will |
| // successfully load. |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| |
| content::FrameTreeNodeId blocked_frame_id = blocked[0]; |
| |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| |
| permission_creator()->SetPermissionResult(true); |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame_id); |
| EXPECT_EQ(permission_creator()->url_requests().size(), 1u); |
| std::string requested_host = permission_creator()->url_requests()[0].host(); |
| |
| EXPECT_EQ(requested_host, kIframeHost2); |
| |
| WaitForNavigationFinished(blocked[0], |
| permission_creator()->url_requests()[0]); |
| |
| EXPECT_FALSE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| |
| histogram_tester.ExpectUniqueSample( |
| supervised_user::SupervisedUserInterstitial:: |
| kInterstitialCommandHistogramName, |
| supervised_user::SupervisedUserInterstitial::Commands:: |
| REMOTE_ACCESS_REQUEST, |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| supervised_user::SupervisedUserInterstitial:: |
| kInterstitialPermissionSourceHistogramName, |
| supervised_user::SupervisedUserInterstitial::RequestPermissionSource:: |
| SUB_FRAME, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, BlockMultipleSubFrames) { |
| BlockHost(kIframeHost1); |
| BlockHost(kIframeHost2); |
| |
| GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 2u); |
| |
| content::FrameTreeNodeId blocked_frame_id_1 = blocked[0]; |
| GURL blocked_frame_url_1 = GetBlockedFrameURL(blocked_frame_id_1); |
| |
| content::FrameTreeNodeId blocked_frame_id_2 = blocked[1]; |
| GURL blocked_frame_url_2 = GetBlockedFrameURL(blocked_frame_id_2); |
| |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame_id_1)); |
| |
| WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| permission_creator()->SetPermissionResult(true); |
| permission_creator()->DelayHandlingForNextRequests(); |
| |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame_id_1); |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame_id_2); |
| |
| EXPECT_EQ(permission_creator()->url_requests().size(), 2u); |
| EXPECT_EQ(permission_creator()->url_requests()[0], GURL(blocked_frame_url_1)); |
| EXPECT_EQ(permission_creator()->url_requests()[1], GURL(blocked_frame_url_2)); |
| |
| NavigationFinishedWaiter waiter1(tab, blocked_frame_id_1, |
| blocked_frame_url_1); |
| NavigationFinishedWaiter waiter2(tab, blocked_frame_id_2, |
| blocked_frame_url_2); |
| |
| permission_creator()->HandleDelayedRequests(); |
| |
| waiter1.Wait(); |
| waiter2.Wait(); |
| |
| DCHECK_EQ(GetBlockedFrames().size(), 0u); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, TestBackButton) { |
| BlockHost(kIframeHost1); |
| |
| GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| |
| permission_creator()->SetPermissionResult(true); |
| permission_creator()->DelayHandlingForNextRequests(); |
| |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked[0]); |
| |
| std::string command = "document.getElementById('back-button').hidden;"; |
| |
| auto* render_frame_host = tracker()->GetHost(blocked[0]); |
| DCHECK(render_frame_host->IsRenderFrameLive()); |
| auto target = content::ToRenderFrameHost(render_frame_host); |
| // Back button should be hidden in iframes. |
| EXPECT_EQ(true, content::EvalJs(target, command, |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, |
| TestBackButtonMainFrame) { |
| BlockHost(kExampleHost); |
| |
| GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| |
| permission_creator()->SetPermissionResult(true); |
| permission_creator()->DelayHandlingForNextRequests(); |
| |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked[0]); |
| |
| std::string command = "document.getElementById('back-button').hidden;"; |
| auto* render_frame_host = tracker()->GetHost(blocked[0]); |
| DCHECK(render_frame_host->IsRenderFrameLive()); |
| |
| auto target = content::ToRenderFrameHost(render_frame_host); |
| bool value = |
| content::EvalJs(target, command, content::EXECUTE_SCRIPT_NO_USER_GESTURE) |
| .ExtractBool(); |
| |
| // Back button should be hidden only when local web approvals is enabled due |
| // to new UI for local web approvals. |
| EXPECT_EQ(value, IsLocalWebApprovalsEnabled()); |
| } |
| |
| // Tests that the trivial www-subdomain stripping is applied on the url |
| // of the interstitial. Blocked urls without further conflicts will be |
| // unblocked by a remote approval. |
| IN_PROC_BROWSER_TEST_F( |
| SupervisedUserIframeFilterTest, |
| BlockedMainFrameFromClassifyUrlForUnstripedHostIsStrippedInRemoteApproval) { |
| // Classify url blocks the navigation to the target url. |
| // No matching blocklist entry exists for the host of the target url. |
| kids_management_api_mock().RestrictSubsequentClassifyUrl(); |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| content::FrameTreeNodeId blocked_frame_id = blocked[0]; |
| GURL blocked_frame_url = GetBlockedFrameURL(blocked_frame_id); |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| |
| // Request remote approval. |
| permission_creator()->SetPermissionResult(true); |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame_id); |
| EXPECT_EQ(permission_creator()->url_requests().size(), 1u); |
| std::string requested_host = permission_creator()->url_requests()[0].host(); |
| |
| // The trivial "www" subdomain is stripped for the url in the remote approval |
| // request. |
| EXPECT_EQ(requested_host, kStrippedExampleHost); |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| // TODO(crbug.com/376229427): Unguard once flakiness removed. |
| WaitForNavigationFinished(blocked_frame_id, blocked_url); |
| // The unstriped url gets unblocked. |
| EXPECT_FALSE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| #endif |
| } |
| |
| // Tests that the url stripping is applied on the url on the interstitial, when |
| // there is no unstriped host entry in the blocklist. |
| IN_PROC_BROWSER_TEST_F( |
| SupervisedUserIframeFilterTest, |
| BlockedMainFrameFromBlockListIsStrippedInRemoteApproval) { |
| // Manual parental blocklist entry blocks the navigation to the target url. |
| BlockHost("*.example.*"); |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| content::FrameTreeNodeId blocked_frame_id = blocked[0]; |
| GURL blocked_frame_url = GetBlockedFrameURL(blocked_frame_id); |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| |
| // Request remote approval. |
| permission_creator()->SetPermissionResult(true); |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame_id); |
| EXPECT_EQ(permission_creator()->url_requests().size(), 1u); |
| std::string requested_host = permission_creator()->url_requests()[0].host(); |
| |
| // The trivial "www" subdomain has been stripped from the host in the |
| // interstitial, because the conflicting entry in the blocklist is not a |
| // www-subdomain conflict. |
| EXPECT_EQ(requested_host, kStrippedExampleHost); |
| } |
| |
| // Tests that the url stripping is skipped on the url on the interstitial, when |
| // there is a unstriped host entry in the blocklist. Blocked urls without |
| // further conflicts will be unblocked by a remote approval. |
| IN_PROC_BROWSER_TEST_F( |
| SupervisedUserIframeFilterTest, |
| BlockedMainFrameFromBlockListForUnstripedHostSkipsStrippingInRemoteApproval) { |
| // Manual parental blocklist entry for the unstriped url blocks the |
| // navigation to the target url. |
| BlockHost(kExampleHost); |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| content::FrameTreeNodeId blocked_frame_id = blocked[0]; |
| GURL blocked_frame_url = GetBlockedFrameURL(blocked_frame_id); |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| |
| // Request remote approval. |
| permission_creator()->SetPermissionResult(true); |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame_id); |
| EXPECT_EQ(permission_creator()->url_requests().size(), 1u); |
| std::string requested_host = permission_creator()->url_requests()[0].host(); |
| |
| // The stripping has been skipped for the url of the interstitial, because an |
| // identical entry exists in the blocklist. The interstitial contains the full |
| // url. |
| EXPECT_EQ(requested_host, kExampleHost); |
| // The navigation gets unblocked. |
| WaitForNavigationFinished(blocked_frame_id, blocked_frame_url); |
| EXPECT_FALSE(IsInterstitialBeingShownInFrame(blocked_frame_id)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, |
| AllowlistedMainFrameDenylistedIframe) { |
| AllowlistHost(kExampleHost); |
| BlockHost(kIframeHost1); |
| |
| GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| auto blocked = GetBlockedFrames(); |
| EXPECT_EQ(blocked.size(), 1u); |
| EXPECT_EQ(kIframeHost1, GetBlockedFrameURL(blocked[0]).host()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, |
| RememberAlreadyRequestedHosts) { |
| BlockHost(kExampleHost); |
| |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked_frames = GetBlockedFrames(); |
| EXPECT_EQ(blocked_frames.size(), 1u); |
| |
| // Expect that remote approvals button is shown. |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frames[0])); |
| // Expect that the local approvals button is shown if the flag is enabled. |
| EXPECT_EQ(IsLocalWebApprovalsEnabled(), |
| IsLocalApprovalsButtonBeingShown(blocked_frames[0])); |
| if (!base::FeatureList::IsEnabled( |
| supervised_user::kSupervisedUserBlockInterstitialV3)) { |
| // Expect that the "Block reason" is shown if we are in interstitial |
| // version 2. The field does not exist in interstitial version 3. |
| EXPECT_TRUE(IsBlockReasonBeingShown(blocked_frames[0])); |
| } |
| |
| // Delay approval/denial by parent. |
| permission_creator()->SetPermissionResult(true); |
| permission_creator()->DelayHandlingForNextRequests(); |
| |
| // Request permission. |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frames[0]); |
| |
| // Navigate to another allowed url. |
| GURL allowed_url = embedded_test_server()->GetURL( |
| kExampleHost2, "/supervised_user/with_iframes.html"); |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), allowed_url)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| // Navigate back to the blocked url. |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| // Error page is being shown, but "Ask Permission" button is not being shown. |
| EXPECT_FALSE(IsRemoteApprovalsButtonBeingShown(blocked_frames[0])); |
| // Expect that the local approvals instead button is shown on the page if the |
| // flag is enabled. |
| EXPECT_EQ(IsLocalWebApprovalsEnabled(), |
| IsLocalApprovalsInsteadButtonBeingShown(blocked_frames[0])); |
| if (!base::FeatureList::IsEnabled( |
| supervised_user::kSupervisedUserBlockInterstitialV3)) { |
| // Expect that the "Block reason" is not shown if we are in interstitial |
| // version 2. The field does not exist in interstitial version 3. |
| EXPECT_FALSE(IsBlockReasonBeingShown(blocked_frames[0])); |
| } |
| |
| content::WebContents* active_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| SupervisedUserNavigationObserver* navigation_observer = |
| SupervisedUserNavigationObserver::FromWebContents(active_contents); |
| ASSERT_NE(navigation_observer, nullptr); |
| |
| EXPECT_TRUE(base::Contains(navigation_observer->requested_hosts_for_test(), |
| kExampleHost)); |
| |
| NavigationFinishedWaiter waiter( |
| active_contents, |
| active_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId(), |
| blocked_url); |
| permission_creator()->HandleDelayedRequests(); |
| waiter.Wait(); |
| |
| EXPECT_FALSE(base::Contains(navigation_observer->requested_hosts_for_test(), |
| kExampleHost)); |
| |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserIframeFilterTest, |
| IFramesWithSameDomainAsMainFrameAllowed) { |
| supervised_user_test_util::SetWebFilterType( |
| browser()->profile(), supervised_user::WebFilterType::kCertainSites); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Allows |www.example.com|. |
| AllowlistHost("*.example.*"); |
| |
| // |with_frames_same_domain.html| contains subframes with "a.example.com" and |
| // "b.example.com", and "c.example2.com" urls. |
| GURL allowed_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes_same_domain.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), allowed_url)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked_frames = GetBlockedFrames(); |
| EXPECT_EQ(blocked_frames.size(), 1u); |
| EXPECT_EQ(GetBlockedFrameURL(blocked_frames[0]).host(), "www.c.example2.com"); |
| } |
| |
| // The switches::kHostWindowBounds commandline flag doesn't appear to work |
| // for tests on other platforms. |
| // TODO(b/300426225): enable these tests on Linux/Mac/Windows. |
| #if BUILDFLAG(IS_CHROMEOS) |
| class SupervisedUserNarrowWidthIframeFilterTest |
| : public SupervisedUserIframeFilterTest { |
| protected: |
| SupervisedUserNarrowWidthIframeFilterTest() = default; |
| ~SupervisedUserNarrowWidthIframeFilterTest() override = default; |
| |
| void SetUp() override; |
| }; |
| |
| void SupervisedUserNarrowWidthIframeFilterTest::SetUp() { |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| ::switches::kHostWindowBounds, "0+0-400x800"); |
| SupervisedUserIframeFilterTest::SetUp(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserNarrowWidthIframeFilterTest, |
| NarrowWidthWindow) { |
| BlockHost(kExampleHost); |
| |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| auto blocked_frames = GetBlockedFrames(); |
| EXPECT_EQ(blocked_frames.size(), 1u); |
| |
| // Expect that remote approvals button is shown. |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frames[0])); |
| // Expect that the local approvals button is shown if the flag is enabled. |
| EXPECT_EQ(IsLocalWebApprovalsEnabled(), |
| IsLocalApprovalsButtonBeingShown(blocked_frames[0])); |
| // Expect that the "Details" link is shown. |
| EXPECT_TRUE(IsDetailsLinkBeingShown(blocked_frames[0])); |
| |
| // Delay approval/denial by parent. |
| permission_creator()->SetPermissionResult(true); |
| permission_creator()->DelayHandlingForNextRequests(); |
| |
| // Request permission. |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frames[0]); |
| |
| // Navigate to another allowed url. |
| GURL allowed_url = embedded_test_server()->GetURL( |
| kExampleHost2, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), allowed_url)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| // Navigate back to the blocked url. |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| // Error page is being shown, but "Ask Permission" button is not being shown. |
| EXPECT_FALSE(IsRemoteApprovalsButtonBeingShown(blocked_frames[0])); |
| // Expect that the local approvals instead button is shown on the page if the |
| // flag is enabled. |
| EXPECT_EQ(IsLocalWebApprovalsEnabled(), |
| IsLocalApprovalsInsteadButtonBeingShown(blocked_frames[0])); |
| // "Details" link is not shown. |
| EXPECT_FALSE(IsDetailsLinkBeingShown(blocked_frames[0])); |
| |
| content::WebContents* active_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| SupervisedUserNavigationObserver* navigation_observer = |
| SupervisedUserNavigationObserver::FromWebContents(active_contents); |
| ASSERT_NE(navigation_observer, nullptr); |
| |
| EXPECT_TRUE(base::Contains(navigation_observer->requested_hosts_for_test(), |
| kExampleHost)); |
| |
| NavigationFinishedWaiter waiter( |
| active_contents, |
| active_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId(), |
| blocked_url); |
| permission_creator()->HandleDelayedRequests(); |
| waiter.Wait(); |
| |
| EXPECT_FALSE(base::Contains(navigation_observer->requested_hosts_for_test(), |
| kExampleHost)); |
| |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| } |
| |
| // Tests Chrome OS local web approvals flow. |
| using ChromeOSLocalWebApprovalsTest = SupervisedUserIframeFilterTest; |
| |
| IN_PROC_BROWSER_TEST_F(ChromeOSLocalWebApprovalsTest, |
| StartLocalWebApprovalsFromMainFrame) { |
| base::HistogramTester histogram_tester; |
| BlockHost(kExampleHost); |
| |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| const std::vector<content::FrameTreeNodeId> blocked_frames = |
| GetBlockedFrames(); |
| ASSERT_EQ(blocked_frames.size(), 1u); |
| const content::FrameTreeNodeId blocked_frame = blocked_frames[0]; |
| EXPECT_TRUE(IsLocalApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frame)); |
| CheckPreferredApprovalButton(blocked_frame); |
| |
| // Trigger local approval flow - native dialog should appear. |
| SendCommandToFrame(kLocalUrlAccessCommand, blocked_frame); |
| EXPECT_TRUE(ash::ParentAccessDialog::GetInstance()); |
| |
| // Close the flow without approval - interstitial should be still shown. |
| ui::test::EventGenerator generator(ash::Shell::Get()->GetPrimaryRootWindow()); |
| generator.PressKey(ui::VKEY_ESCAPE, ui::EF_NONE); |
| |
| EXPECT_FALSE(ash::ParentAccessDialog::GetInstance()); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| EXPECT_TRUE(IsLocalApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frame)); |
| CheckPreferredApprovalButton(blocked_frame); |
| |
| histogram_tester.ExpectUniqueSample( |
| supervised_user::SupervisedUserInterstitial:: |
| kInterstitialCommandHistogramName, |
| supervised_user::SupervisedUserInterstitial::Commands:: |
| LOCAL_ACCESS_REQUEST, |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| supervised_user::SupervisedUserInterstitial:: |
| kInterstitialPermissionSourceHistogramName, |
| supervised_user::SupervisedUserInterstitial::RequestPermissionSource:: |
| MAIN_FRAME, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ChromeOSLocalWebApprovalsTest, |
| StartLocalWebApprovalsFromIframe) { |
| base::HistogramTester histogram_tester; |
| BlockHost(kIframeHost1); |
| |
| const GURL allowed_url_with_iframes = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/with_iframes.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), allowed_url_with_iframes)); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| const std::vector<content::FrameTreeNodeId> blocked_frames = |
| GetBlockedFrames(); |
| ASSERT_EQ(blocked_frames.size(), 1u); |
| const content::FrameTreeNodeId blocked_frame = blocked_frames[0]; |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame)); |
| EXPECT_TRUE(IsLocalApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frame)); |
| CheckPreferredApprovalButton(blocked_frame); |
| |
| // Trigger local approval flow - native dialog should appear. |
| SendCommandToFrame(kLocalUrlAccessCommand, blocked_frame); |
| EXPECT_TRUE(ash::ParentAccessDialog::GetInstance()); |
| |
| // Close the flow without approval - interstitial should be still shown. |
| ui::test::EventGenerator generator(ash::Shell::Get()->GetPrimaryRootWindow()); |
| generator.PressKey(ui::VKEY_ESCAPE, ui::EF_NONE); |
| |
| EXPECT_FALSE(ash::ParentAccessDialog::GetInstance()); |
| EXPECT_FALSE(IsInterstitialBeingShownInMainFrame(browser())); |
| EXPECT_TRUE(IsInterstitialBeingShownInFrame(blocked_frame)); |
| EXPECT_TRUE(IsLocalApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frame)); |
| CheckPreferredApprovalButton(blocked_frame); |
| |
| histogram_tester.ExpectUniqueSample( |
| supervised_user::SupervisedUserInterstitial:: |
| kInterstitialCommandHistogramName, |
| supervised_user::SupervisedUserInterstitial::Commands:: |
| LOCAL_ACCESS_REQUEST, |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| supervised_user::SupervisedUserInterstitial:: |
| kInterstitialPermissionSourceHistogramName, |
| supervised_user::SupervisedUserInterstitial::RequestPermissionSource:: |
| SUB_FRAME, |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ChromeOSLocalWebApprovalsTest, |
| UpdateUIAfterRemoteRequestSent) { |
| BlockHost(kExampleHost); |
| |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| |
| const std::vector<content::FrameTreeNodeId> blocked_frames = |
| GetBlockedFrames(); |
| ASSERT_EQ(blocked_frames.size(), 1u); |
| const content::FrameTreeNodeId blocked_frame = blocked_frames[0]; |
| EXPECT_TRUE(IsLocalApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_TRUE(IsRemoteApprovalsButtonBeingShown(blocked_frame)); |
| |
| // Trigger remote approval flow - ui should change. |
| SendCommandToFrame(kRemoteUrlAccessCommand, blocked_frame); |
| |
| EXPECT_TRUE(IsInterstitialBeingShownInMainFrame(browser())); |
| EXPECT_FALSE(IsLocalApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_FALSE(IsRemoteApprovalsButtonBeingShown(blocked_frame)); |
| EXPECT_TRUE(IsLocalApprovalsInsteadButtonBeingShown(blocked_frame)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| class SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers |
| : public SupervisedUserNavigationThrottleTestBase, |
| public testing::WithParamInterface< |
| supervised_user::SupervisionMixin::SignInMode> { |
| protected: |
| static supervised_user::SupervisionMixin::SignInMode GetSignInMode() { |
| return GetParam(); |
| } |
| SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers() |
| : SupervisedUserNavigationThrottleTestBase(GetSignInMode()) {} |
| ~SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers() override = |
| default; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers, |
| |
| testing::Values(supervised_user::SupervisionMixin::SignInMode::kRegular, |
| supervised_user::SupervisionMixin::SignInMode::kSupervised), |
| [](const testing::TestParamInfo< |
| supervised_user::SupervisionMixin::SignInMode>& info) { |
| return base::StrCat({"WithSignInMode", SignInModeAsString(info.param)}); |
| }); |
| |
| IN_PROC_BROWSER_TEST_P( |
| SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers, |
| CheckAgainstBlocklist) { |
| BlockHost(kExampleHost2); |
| |
| // Classify url is never called - if ever, a static blocklist should be used. |
| EXPECT_CALL(kids_management_api_mock().classify_url_mock(), |
| ClassifyUrl(::testing::_)) |
| .Times(0); |
| |
| GURL blocked_url = embedded_test_server()->GetURL( |
| kExampleHost2, "/supervised_user/simple.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), blocked_url)); |
| |
| // Only supervised users should experience the interstitial |
| EXPECT_EQ(IsInterstitialBeingShownInMainFrame(browser()), |
| GetSignInMode() == |
| supervised_user::SupervisionMixin::SignInMode::kSupervised); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers, |
| CheckAgainstClassifyUrlRPC) { |
| kids_management_api_mock().RestrictSubsequentClassifyUrl(); |
| GURL url = embedded_test_server()->GetURL(kExampleHost2, |
| "/supervised_user/simple.html"); |
| |
| // Only supervised users should call the backend |
| if (GetSignInMode() == |
| supervised_user::SupervisionMixin::SignInMode::kSupervised) { |
| EXPECT_CALL(kids_management_api_mock().classify_url_mock(), |
| ClassifyUrl(supervised_user::Classifies(url))) |
| .Times(1); |
| } else { |
| EXPECT_CALL(kids_management_api_mock().classify_url_mock(), |
| ClassifyUrl(::testing::_)) |
| .Times(0); |
| } |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Only supervised users should experience the |
| // interstitial |
| EXPECT_EQ(IsInterstitialBeingShownInMainFrame(browser()), |
| GetSignInMode() == |
| supervised_user::SupervisionMixin::SignInMode::kSupervised); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SupervisedUserNavigationThrottleOnlyEnabledForSupervisedUsers, |
| CheckSameDocumentNavigationAgainstClassifyUrlRPC) { |
| kids_management_api_mock().RestrictSubsequentClassifyUrl(); |
| GURL url = embedded_test_server()->GetURL(kExampleHost2, |
| "/supervised_user/simple.html"); |
| |
| // Only supervised users should call the backend |
| if (GetSignInMode() == |
| supervised_user::SupervisionMixin::SignInMode::kSupervised) { |
| EXPECT_CALL(kids_management_api_mock().classify_url_mock(), |
| ClassifyUrl(supervised_user::Classifies(url))) |
| .Times(1); |
| } else { |
| EXPECT_CALL(kids_management_api_mock().classify_url_mock(), |
| ClassifyUrl(::testing::_)) |
| .Times(0); |
| } |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // Only supervised users should experience the |
| // interstitial |
| EXPECT_EQ(IsInterstitialBeingShownInMainFrame(browser()), |
| GetSignInMode() == |
| supervised_user::SupervisionMixin::SignInMode::kSupervised); |
| |
| // Simulate a same document navigation by navigating to #test. |
| GURL::Replacements replace_ref; |
| replace_ref.SetRefStr("test"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), |
| url.ReplaceComponents(replace_ref))); |
| |
| // Only supervised users should experience the |
| // interstitial |
| EXPECT_EQ(IsInterstitialBeingShownInMainFrame(browser()), |
| GetSignInMode() == |
| supervised_user::SupervisionMixin::SignInMode::kSupervised); |
| } |
| |
| class SupervisedUserNavigationThrottleFencedFramesTest |
| : public SupervisedUserNavigationThrottleTestBase { |
| public: |
| SupervisedUserNavigationThrottleFencedFramesTest() |
| : SupervisedUserNavigationThrottleTestBase( |
| supervised_user::SupervisionMixin::SignInMode::kSupervised) {} |
| ~SupervisedUserNavigationThrottleFencedFramesTest() override = default; |
| SupervisedUserNavigationThrottleFencedFramesTest( |
| const SupervisedUserNavigationThrottleFencedFramesTest&) = delete; |
| |
| SupervisedUserNavigationThrottleFencedFramesTest& operator=( |
| const SupervisedUserNavigationThrottleFencedFramesTest&) = delete; |
| |
| content::test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return fenced_frame_helper_; |
| } |
| |
| private: |
| content::test::FencedFrameTestHelper fenced_frame_helper_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserNavigationThrottleFencedFramesTest, |
| BlockFencedFrame) { |
| BlockHost(kIframeHost2); |
| const GURL kInitialUrl = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/simple.html"); |
| |
| kids_management_api_mock().AllowSubsequentClassifyUrl(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kInitialUrl)); |
| |
| // Same origin fenced frame is not blocked, and therefore must be allowed. |
| const GURL kSameOriginFencedFrameUrl = embedded_test_server()->GetURL( |
| kExampleHost, "/supervised_user/fenced_frame.html"); |
| content::RenderFrameHost* rfh_same_origin = |
| fenced_frame_test_helper().CreateFencedFrame( |
| web_contents()->GetPrimaryMainFrame(), kSameOriginFencedFrameUrl); |
| EXPECT_TRUE(rfh_same_origin); |
| |
| // Host1 is not blocked, and therefore must be allowed. |
| const GURL kHost1FencedFrameUrl = embedded_test_server()->GetURL( |
| kIframeHost1, "/supervised_user/fenced_frame.html"); |
| content::RenderFrameHost* rfh_host1 = |
| fenced_frame_test_helper().CreateFencedFrame( |
| web_contents()->GetPrimaryMainFrame(), kHost1FencedFrameUrl); |
| EXPECT_TRUE(rfh_host1); |
| |
| // Host2 is blocked, and therefore should result in a interstitial being |
| // shown, which is validated by the expectation of net::Error::ERR_FAILED. |
| const GURL kHost2FencedFrameUrl = embedded_test_server()->GetURL( |
| kIframeHost2, "/supervised_user/fenced_frame.html"); |
| content::RenderFrameHost* rfh_host2 = |
| fenced_frame_test_helper().CreateFencedFrame( |
| web_contents()->GetPrimaryMainFrame(), kHost2FencedFrameUrl, |
| net::Error::ERR_FAILED); |
| EXPECT_TRUE(rfh_host2); |
| } |
| |
| } // namespace |