chromium/chrome/browser/ui/webui/policy/policy_ui_browsertest.cc

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

#include <stddef.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/cfi_buildflags.h"
#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/simple_test_clock.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/policy/profile_policy_connector_builder.h"
#include "chrome/browser/policy/schema_registry_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/browser/webui/policy_status_provider.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/external_data_fetcher.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/policy_constants.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/login/test/device_state_mixin.h"
#include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/install_verifier.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/account_id/account_id.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/simple_feature.h"
#endif  // !BUILDFLAG(IS_ANDROID)

_;
Return;

namespace {

// Allows waiting until the policy schema for a |PolicyNamespace| has been made
// available by a |Profile|'s |SchemaRegistry|.
class PolicySchemaAvailableWaiter : public policy::SchemaRegistry::Observer {};

std::vector<std::string> PopulateExpectedPolicy(
    const std::string& name,
    const std::string& value,
    const std::string& source,
    const policy::PolicyMap::Entry* policy_map_entry,
    bool unknown) {}
}  // namespace

class PolicyUITest : public PlatformBrowserTest {};

PolicyUITest::PolicyUITest() = default;

PolicyUITest::~PolicyUITest() = default;

void PolicyUITest::SetUpInProcessBrowserTestFixture() {}

void PolicyUITest::UpdateProviderPolicyForNamespace(
    const policy::PolicyNamespace& policy_namespace,
    const policy::PolicyMap& policy) {}

void PolicyUITest::VerifyPolicies(
    const std::vector<std::vector<std::string>>& expected_policies) {}

void PolicyUITest::VerifyReportButton(bool visible) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
class PolicyUIStatusTest : public MixinBasedInProcessBrowserTest {
 public:
  void SetUpOnMainThread() override {
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
    logged_in_user_mixin_.LogInUser();
    // By default DeviceStateMixin sets public key version to 17 whereas policy
    // test server inside LoggedInUserMixin has only one version. By setting
    // public_key_version to 1, we make device policy requests succeed and thus
    // device policy timestamp set.
    device_state_.RequestDevicePolicyUpdate()
        ->policy_data()
        ->set_public_key_version(1);
  }

  bool ReadStatusFor(const std::string& policy_legend,
                     base::flat_map<std::string, std::string>* policy_status);
  bool ReloadPolicies();
  bool ReloadPolicies(content::WebContents* contents);

 protected:
  ash::DeviceStateMixin device_state_{
      &mixin_host_,
      ash::DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
  ash::LoggedInUserMixin logged_in_user_mixin_{
      &mixin_host_, /*test_base=*/this, embedded_test_server(),
      ash::LoggedInUserMixin::LogInType::kManaged};
};

bool PolicyUIStatusTest::ReadStatusFor(
    const std::string& policy_legend,
    base::flat_map<std::string, std::string>* policy_status) {
  // Retrieve the text contents of the status table with specified heading.
  const std::string javascript = R"JS(
    (function() {
      function readStatus() {
        // Wait for the status box to appear in case page just loaded.
        const statusSection = document.getElementById('status-section');
        if (statusSection.hidden) {
          return new Promise(resolve => {
            window.requestIdleCallback(resolve);
          }).then(readStatus);
        }

        const policies = getPolicyFieldsets();
        const statuses = {};
        for (let i = 0; i < policies.length; ++i) {
          const statusHeading = policies[i]
            .querySelector('.status-box-heading').textContent;
          const entries = {};
          const rows = policies[i]
            .querySelectorAll('.status-entry div:nth-child(2)');
          for (let j = 0; j < rows.length; ++j) {
            entries[rows[j].className.split(' ')[0]] = rows[j].textContent
              .trim();
          }
          statuses[statusHeading.trim()] = entries;
        }
        return JSON.stringify(statuses);
      };
      return new Promise(resolve => {
        window.requestIdleCallback(resolve);
      }).then(readStatus);
    })();
  )JS";
  content::WebContents* contents =
      chrome_test_utils::GetActiveWebContents(this);
  std::string json = content::EvalJs(contents, javascript).ExtractString();
  std::optional<base::Value> statuses = base::JSONReader::Read(json);
  if (!statuses.has_value() || !statuses->is_dict())
    return false;
  const base::Value::Dict& status_dict = statuses->GetDict();
  const base::Value::Dict* actual_entries = status_dict.FindDict(policy_legend);
  if (!actual_entries) {
    return false;
  }
  for (const auto entry : *actual_entries) {
    policy_status->insert_or_assign(entry.first, entry.second.GetString());
  }
  return true;
}

bool PolicyUIStatusTest::ReloadPolicies() {
  content::WebContents* contents =
      chrome_test_utils::GetActiveWebContents(this);
  return ReloadPolicies(contents);
}

bool PolicyUIStatusTest::ReloadPolicies(content::WebContents* contents) {
  const std::string javascript = R"JS(
    (function() {
      const reloadPoliciesBtn = document.getElementById('reload-policies');
      reloadPoliciesBtn.click();
      // Wait until reload button becomes enabled again, i.e. policies reloaded.
      function waitForPoliciesToReload() {
        if (reloadPoliciesBtn.disabled) {
          return new Promise(resolve => {
            window.requestIdleCallback(resolve);
          }).then(waitForPoliciesToReload);
        } else {
          return true;
        }
      }
      return new Promise(resolve => {
        window.requestIdleCallback(resolve);
      }).then(waitForPoliciesToReload);
    })();
  )JS";
  return content::ExecJs(contents, javascript);
}

#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(PolicyUIStatusTest, CheckPolicyUiInGuestProfile) {
  // Verifies that the page opens in guest session.
  const Browser* policy_browser = OpenURLOffTheRecord(
      browser()->profile(), GURL(chrome::kChromeUIPolicyURL));
  ASSERT_TRUE(policy_browser);
  content::WebContents* contents =
      policy_browser->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ReloadPolicies(contents));
}
#endif  // !BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_F(PolicyUIStatusTest,
                       ShowsZeroSecondsSinceRefreshAfterReloadingPolicies) {
  // Verifies that the time since refresh of a policy set is set to 0 seconds
  // after "Reload policies" button is pressed and policies are reloaded.

  // Mock time in policy server and classes used by refresh logic.
  base::Time now = base::Time::Now();
  logged_in_user_mixin_.GetEmbeddedPolicyTestServerMixin()
      ->UpdatePolicyTimestamp(now);
  base::SimpleTestClock status_provider_clock_mock;
  status_provider_clock_mock.SetNow(now);
  auto status_provider_clock_mock_closure =
      policy::PolicyStatusProvider::OverrideClockForTesting(
          &status_provider_clock_mock);
  base::SimpleTestClock policy_refresher_clock_mock;
  policy_refresher_clock_mock.SetNow(now);
  auto policy_refresher_clock_mock_closure =
      policy::CloudPolicyRefreshScheduler::OverrideClockForTesting(
          &policy_refresher_clock_mock);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           GURL(chrome::kChromeUIPolicyURL)));
  ASSERT_TRUE(ReloadPolicies());

  base::flat_map<std::string, std::string> status;
  ASSERT_TRUE(ReadStatusFor("User policies", &status));
  EXPECT_EQ(status["time-since-last-refresh"], "0 secs ago");
  EXPECT_EQ(status["time-since-last-fetch-attempt"], "0 secs ago");
  ASSERT_TRUE(ReadStatusFor("Device policies", &status));
  EXPECT_EQ(status["time-since-last-refresh"], "0 secs ago");
  EXPECT_EQ(status["time-since-last-fetch-attempt"], "0 secs ago");
}

IN_PROC_BROWSER_TEST_F(PolicyUIStatusTest, ShowsCorrectTimesSinceRefresh) {
  // Verifies that the time since refresh of a policy set is correctly computed.

  // Mock time in policy server and classes used by refresh logic.
  base::Time now = base::Time::Now();
  logged_in_user_mixin_.GetEmbeddedPolicyTestServerMixin()
      ->UpdatePolicyTimestamp(now);
  base::SimpleTestClock status_provider_clock_mock;
  status_provider_clock_mock.SetNow(now);
  auto status_provider_clock_mock_closure =
      policy::PolicyStatusProvider::OverrideClockForTesting(
          &status_provider_clock_mock);
  base::SimpleTestClock policy_refresher_clock_mock;
  policy_refresher_clock_mock.SetNow(now);
  auto policy_refresher_clock_mock_closure =
      policy::CloudPolicyRefreshScheduler::OverrideClockForTesting(
          &policy_refresher_clock_mock);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           GURL(chrome::kChromeUIPolicyURL)));
  ASSERT_TRUE(ReloadPolicies());
  status_provider_clock_mock.Advance(base::Hours(1));
  policy_refresher_clock_mock.Advance(base::Hours(1));
  // Refresh the page without reloading policies.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           GURL(chrome::kChromeUIPolicyURL)));
  base::RunLoop().RunUntilIdle();  // Ensure status request has been processed.

  base::flat_map<std::string, std::string> status;
  ASSERT_TRUE(ReadStatusFor("User policies", &status));
  EXPECT_EQ(status["time-since-last-refresh"], "1 hour ago");
  EXPECT_EQ(status["time-since-last-fetch-attempt"], "1 hour ago");
  ASSERT_TRUE(ReadStatusFor("Device policies", &status));
  EXPECT_EQ(status["time-since-last-refresh"], "1 hour ago");
  EXPECT_EQ(status["time-since-last-fetch-attempt"], "1 hour ago");
}

IN_PROC_BROWSER_TEST_F(PolicyUIStatusTest,
                       ShowsCorrectRefreshTimesAfterFailedReload) {
  // Verifies that the time since refresh of a policy set is correctly updated
  // after a failed attempt to update policies.

  // Mock time in policy server and classes used by refresh logic.
  base::Time now = base::Time::Now();
  logged_in_user_mixin_.GetEmbeddedPolicyTestServerMixin()
      ->UpdatePolicyTimestamp(now);
  base::SimpleTestClock status_provider_clock_mock;
  status_provider_clock_mock.SetNow(now);
  auto status_provider_clock_mock_closure =
      policy::PolicyStatusProvider::OverrideClockForTesting(
          &status_provider_clock_mock);
  base::SimpleTestClock policy_refresher_clock_mock;
  policy_refresher_clock_mock.SetNow(now);
  auto policy_refresher_clock_mock_closure =
      policy::CloudPolicyRefreshScheduler::OverrideClockForTesting(
          &policy_refresher_clock_mock);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           GURL(chrome::kChromeUIPolicyURL)));
  ASSERT_TRUE(ReloadPolicies());
  logged_in_user_mixin_.GetEmbeddedPolicyTestServerMixin()->SetPolicyFetchError(
      500);
  status_provider_clock_mock.Advance(base::Hours(1));
  policy_refresher_clock_mock.Advance(base::Hours(1));
  ASSERT_TRUE(ReloadPolicies());

  base::flat_map<std::string, std::string> status;
  ASSERT_TRUE(ReadStatusFor("User policies", &status));
  EXPECT_EQ(status["time-since-last-refresh"], "1 hour ago");
  EXPECT_EQ(status["time-since-last-fetch-attempt"], "0 secs ago");
  ASSERT_TRUE(ReadStatusFor("Device policies", &status));
  EXPECT_EQ(status["time-since-last-refresh"], "1 hour ago");
  EXPECT_EQ(status["time-since-last-fetch-attempt"], "0 secs ago");
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

IN_PROC_BROWSER_TEST_F(PolicyUITest, SendPolicyNames) {}

IN_PROC_BROWSER_TEST_F(PolicyUITest, SendPolicyValues) {}

IN_PROC_BROWSER_TEST_F(PolicyUITest, ReportButton) {}

IN_PROC_BROWSER_TEST_F(PolicyUITest, ReportButtonWithProfileReporting) {}

#if !BUILDFLAG(IS_CHROMEOS)
class PolicyPrecedenceUITest
    : public PolicyUITest,
      public ::testing::WithParamInterface<std::tuple<
          /*cloud_policy_overrides_platform_policy=*/bool,
          /*cloud_user_policy_overrides_cloud_machine_policy=*/bool,
          /*is_user_affiliated=*/bool>> {};

// Verify that the precedence order displayed in the Policy Precedence table is
// correct.
IN_PROC_BROWSER_TEST_P(PolicyPrecedenceUITest, PrecedenceOrder) {}

INSTANTIATE_TEST_SUITE_P();
#endif  // !BUILDFLAG(IS_CHROMEOS)

#if !BUILDFLAG(IS_ANDROID)
// TODO(https://crbug.com/1027135) Add tests to verify extension policies are
// exported correctly.
class ExtensionPolicyUITest : public PolicyUITest,
                              public ::testing::WithParamInterface<bool> {};

// TODO(crbug.com/41429868) Flaky time outs on Linux Chromium OS ASan
// LSan bot.
#if defined(ADDRESS_SANITIZER)
#define MAYBE_ExtensionLoadAndSendPolicy
#else
#define MAYBE_ExtensionLoadAndSendPolicy
#endif
IN_PROC_BROWSER_TEST_P(ExtensionPolicyUITest,
                       MAYBE_ExtensionLoadAndSendPolicy) {}

INSTANTIATE_TEST_SUITE_P();

#endif  // !BUILDFLAG(IS_ANDROID)