chromium/ios/chrome/browser/supervised_user/model/supervised_user_url_filter_tab_helper_unittest.mm

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/supervised_user/model/supervised_user_url_filter_tab_helper.h"

#import "base/memory/scoped_refptr.h"
#import "base/task/single_thread_task_runner.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/identity_test_utils.h"
#import "components/supervised_user/core/browser/supervised_user_service.h"
#import "components/supervised_user/core/browser/supervised_user_settings_service.h"
#import "components/supervised_user/core/browser/supervised_user_utils.h"
#import "components/supervised_user/core/common/features.h"
#import "components/supervised_user/core/common/supervised_user_constants.h"
#import "components/supervised_user/test_support/supervised_user_signin_test_utils.h"
#import "components/sync_preferences/pref_service_mock_factory.h"
#import "components/sync_preferences/pref_service_syncable.h"
#import "ios/chrome/browser/shared/model/prefs/browser_prefs.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/identity_test_environment_browser_state_adaptor.h"
#import "ios/chrome/browser/supervised_user/model/child_account_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_capabilities.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_error_container.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_settings_service_factory.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/web/public/navigation/web_state_policy_decider.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#import "services/network/test/test_url_loader_factory.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"

namespace {

const char kTestEmail[] = "[email protected]";
NSString* kExampleURL = @"http://example.com";

}  // namespace

class SupervisedUserURLFilterTabHelperTest : public PlatformTest {
 protected:
  SupervisedUserURLFilterTabHelperTest() {
    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        IdentityManagerFactory::GetInstance(),
        base::BindRepeating(IdentityTestEnvironmentBrowserStateAdaptor::
                                BuildIdentityManagerForTests));

    chrome_browser_state_ = std::move(builder).Build();
    web_state_.SetBrowserState(chrome_browser_state_.get());
    SupervisedUserURLFilterTabHelper::CreateForWebState(&web_state_);
    SupervisedUserErrorContainer::CreateForWebState(&web_state_);
    security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
        &web_state_);
    scoped_feature_list_.InitAndEnableFeature(
        supervised_user::kReplaceSupervisionPrefsWithAccountCapabilitiesOnIOS);
  }

  // Signs the user into `email` as the primary Chrome account and sets the
  // given parental control capabilities on this account.
  void SignIn(const std::string& email, bool is_subject_to_parental_controls) {
    signin::IdentityManager* identity_manager =
        IdentityManagerFactory::GetForBrowserState(chrome_browser_state_.get());
    AccountInfo account = signin::MakePrimaryAccountAvailable(
        identity_manager, email, signin::ConsentLevel::kSignin);
    supervised_user::UpdateSupervisionStatusForAccount(
        account, identity_manager, is_subject_to_parental_controls);

    // Initialize supervised_user services.
    ChildAccountServiceFactory::GetForBrowserState(chrome_browser_state_.get())
        ->Init();

    supervised_user::SupervisedUserService* supervised_user_service =
        SupervisedUserServiceFactory::GetForBrowserState(
            chrome_browser_state_.get());
    supervised_user_service->Init();

    EXPECT_EQ(supervised_user::IsSubjectToParentalControls(
                  chrome_browser_state_.get()),
              is_subject_to_parental_controls);
  }

  // Calls `ShouldAllowRequest` for a request with the given `url_string`.
  // Returns true if the URL request is blocked.
  bool IsURLBlocked(NSString* url_string) {
    // Set up for `ShouldAllowRequest`.
    const web::WebStatePolicyDecider::RequestInfo request_info(
        ui::PageTransition::PAGE_TRANSITION_LINK, /*target_frame_is_main=*/true,
        /*target_frame_is_cross_origin=*/false,
        /*target_window_is_cross_origin=*/false,
        /*is_user_initiated=*/false, /*user_tapped_recently=*/false);
    __block bool callback_called = false;
    __block web::WebStatePolicyDecider::PolicyDecision request_policy =
        web::WebStatePolicyDecider::PolicyDecision::Allow();
    base::RunLoop run_loop;
    auto quit_closure = run_loop.QuitClosure();
    auto callback =
        base::BindOnce(^(web::WebStatePolicyDecider::PolicyDecision decision) {
          request_policy = decision;
          callback_called = true;
          std::move(quit_closure).Run();
        });
    web_state_.ShouldAllowRequest(
        [NSURLRequest requestWithURL:[NSURL URLWithString:url_string]],
        request_info, std::move(callback));
    run_loop.Run();
    EXPECT_TRUE(callback_called);

    return request_policy.ShouldCancelNavigation();
  }

  void AllowExampleSiteForSupervisedUser() {
    supervised_user::SupervisedUserService* supervised_user_service =
        SupervisedUserServiceFactory::GetForBrowserState(
            chrome_browser_state_.get());

    std::map<std::string, bool> hosts;
    hosts["example.com"] = true;
    supervised_user_service->GetURLFilter()->SetManualHosts(hosts);
    supervised_user_service->GetURLFilter()->SetDefaultFilteringBehavior(
        supervised_user::FilteringBehavior::kAllow);
  }

  void RestrictAllSitesForSupervisedUser() {
    supervised_user::SupervisedUserService* supervised_user_service =
        SupervisedUserServiceFactory::GetForBrowserState(
            chrome_browser_state_.get());
    supervised_user_service->GetURLFilter()->SetDefaultFilteringBehavior(
        supervised_user::FilteringBehavior::kBlock);
  }

 private:
  web::WebTaskEnvironment task_environment_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
  web::FakeWebState web_state_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(SupervisedUserURLFilterTabHelperTest,
       BlockCertainSitesForSupervisedUser) {
  base::HistogramTester histogram_tester;
  SignIn(kTestEmail,
         /*is_subject_to_parental_controls=*/true);
  RestrictAllSitesForSupervisedUser();
  EXPECT_TRUE(IsURLBlocked(kExampleURL));
  histogram_tester.ExpectTotalCount(
      supervised_user::kSupervisedUserURLFilteringResultHistogramName, 1);

  AllowExampleSiteForSupervisedUser();
  EXPECT_FALSE(IsURLBlocked(kExampleURL));
  histogram_tester.ExpectTotalCount(
      supervised_user::kSupervisedUserURLFilteringResultHistogramName, 2);
}

TEST_F(SupervisedUserURLFilterTabHelperTest,
       AllowsAllSitesForNonSupervisedUser) {
  base::HistogramTester histogram_tester;
  SignIn(kTestEmail,
         /*is_subject_to_parental_controls=*/false);
  RestrictAllSitesForSupervisedUser();
  EXPECT_FALSE(IsURLBlocked(kExampleURL));
  AllowExampleSiteForSupervisedUser();
  EXPECT_FALSE(IsURLBlocked(kExampleURL));

  // This histogram is only relevant for supervised users.
  histogram_tester.ExpectTotalCount(
      supervised_user::kSupervisedUserURLFilteringResultHistogramName, 0);
}

TEST_F(SupervisedUserURLFilterTabHelperTest, AllowsAllSitesWhenLoggedOut) {
  RestrictAllSitesForSupervisedUser();
  EXPECT_FALSE(IsURLBlocked(kExampleURL));
  AllowExampleSiteForSupervisedUser();
  EXPECT_FALSE(IsURLBlocked(kExampleURL));
}