chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_apitest.cc

// 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 <memory>
#include <string>
#include <utility>

#include "base/test/scoped_feature_list.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/chrome_signin_client_test_util.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/policy/core/common/management/management_service.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/version_info/version_info.h"
#include "content/public/test/browser_test.h"
#include "extensions/common/extension.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "services/network/test/test_url_loader_factory.h"

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#include "base/files/file_path.h"
#include "base/process/process.h"
#include "base/strings/string_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "components/device_signals/core/common/signals_features.h"
#include "components/device_signals/core/system_signals/platform_utils.h"  // nogncheck
#endif  //  BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#include "components/device_signals/test/test_constants.h"
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)
#include "base/strings/sys_string_conversions.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "components/device_signals/test/win/scoped_executable_files.h"
#endif  // BUILDFLAG(IS_WIN)

#if !BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"
#include "components/enterprise/browser/controller/fake_browser_dm_token_storage.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/policy/core/common/cloud/cloud_policy_store.h"
#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#include "components/policy/proto/device_management_backend.pb.h"
#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS)
#include "base/strings/strcat.h"
#include "chrome/browser/enterprise/util/affiliation.h"
#include "chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h"
#endif

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/login/test/cryptohome_mixin.h"
#include "chrome/browser/ash/policy/affiliation/affiliation_mixin.h"
#include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#endif

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/browser_process.h"
#include "chromeos/startup/browser_init_params.h"
#include "components/policy/core/common/policy_loader_lacros.h"
#endif

namespace extensions {
namespace {

#if !BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kAffiliationId[] =;
#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)

// Manifest key for the Endpoint Verification extension found at
// chrome.google.com/webstore/detail/callobklhcbilhphinckomhgkigmfocg
// This extension is authorized to use the enterprise.reportingPrivate API.
constexpr char kAuthorizedManifestKey[] =;

// Manifest key for the Google Translate extension found at
// chrome.google.com/webstore/detail/aapbdbdomjkkjkaonfhkkikfgjllcleb
// This extension is unauthorized to use the enterprise.reportingPrivate API.
constexpr char kUnauthorizedManifestKey[] =;

constexpr char kManifestTemplate[] =;

}  // namespace

// This test class is to validate that the API is correctly unavailable on
// unsupported extensions and unsupported platforms. It also does basic
// validation that fields are present in the values the API returns, but it
// doesn't make strong assumption on what those values are to minimize the kind
// of mocking that is already done in unit/browser tests covering this API.
class EnterpriseReportingPrivateApiTest : public extensions::ExtensionApiTest {};

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest,
                       ExtensionAvailability) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetDeviceId) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetPersistentSecret) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetDeviceData) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, SetDeviceData) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetDeviceInfo) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetContextInfo) {}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetCertificate) {}

#if BUILDFLAG(IS_WIN)

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetAvInfo_Success) {
  constexpr char kTest[] = R"(
      chrome.test.assertEq(
        'function',
        typeof chrome.enterprise.reportingPrivate.getAvInfo);
      const userContext = {userId: '%s'};

   chrome.enterprise.reportingPrivate.getAvInfo(userContext, (avProducts) => {
        chrome.test.assertNoLastError();
        chrome.test.assertTrue(avProducts instanceof Array);
        chrome.test.notifyPass();
      });
  )";

  AccountInfo account_info = SignIn("[email protected]");
  RunTest(base::StringPrintf(kTest, account_info.gaia.c_str()));
}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest, GetHotfixes_Success) {
  constexpr char kTest[] = R"(
      chrome.test.assertEq(
        'function',
        typeof chrome.enterprise.reportingPrivate.getHotfixes);
      const userContext = {userId: '%s'};

   chrome.enterprise.reportingPrivate.getHotfixes(userContext, (hotfixes) => {
        chrome.test.assertNoLastError();
        chrome.test.assertTrue(hotfixes instanceof Array);
        chrome.test.notifyPass();
      });
  )";

  AccountInfo account_info = SignIn("[email protected]");
  RunTest(base::StringPrintf(kTest, account_info.gaia.c_str()));
}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest,
                       GetRegistrySettings_Success) {
  constexpr char kTest[] = R"(
      chrome.test.assertEq(
        'function',
        typeof chrome.enterprise.reportingPrivate.getSettings);
      const userContext = {userId: '%s'};
      const options = [];

      %s

      const request = {userContext, options};

   chrome.enterprise.reportingPrivate.getSettings(
    request,
    (settingsItems) => {
        %s
    });
  )";

  std::string kOptions = "";

  std::string registry_path = "SOFTWARE\\\\Chromium\\\\DeviceTrust\\\\Test";
  std::string valid_key = "test_key";

  kOptions = base::StringPrintf(
      R"(
    const test_hive = 'HKEY_CURRENT_USER';
    const registry_path = '%s';
    const invalid_path = 'SOFTWARE\\Chromium\\DeviceTrust\\Invalid';
    const valid_key = '%s';
    const invalid_key = 'invalid_key';

    options.push({
      hive: test_hive,
      path: registry_path,
      key: valid_key,
      getValue: false
    });
    options.push({
      hive: test_hive,
      path: registry_path,
      key: valid_key,
      getValue: true
    });
    options.push({
      hive: test_hive,
      path: registry_path,
      key: invalid_key,
      getValue: true
    });
    options.push({
      hive: test_hive,
      path: invalid_path,
      key: valid_key,
      getValue: true
    });
  )",
      registry_path.c_str(), valid_key.c_str());

  registry_util::RegistryOverrideManager registry_override_manager_;
  registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER);

  base::win::RegKey key(HKEY_CURRENT_USER,
                        base::SysUTF8ToWide(registry_path).c_str(),
                        KEY_ALL_ACCESS);
  ASSERT_TRUE(key.WriteValue(base::SysUTF8ToWide(valid_key).c_str(), 37) ==
              ERROR_SUCCESS);

  constexpr char kAssertions[] = R"(
      chrome.test.assertNoLastError();
      chrome.test.assertTrue(settingsItems instanceof Array);
      chrome.test.assertEq(4, settingsItems.length);

      const expectedItems = [];

      expectedItems.push({
        hive: test_hive,
        path: registry_path,
        key: valid_key,
        presence: 'FOUND',
      });
      expectedItems.push({
        hive: test_hive,
        path: registry_path,
        key: valid_key,
        presence: 'FOUND',
        value: '37',
      });
      expectedItems.push({
        hive: test_hive,
        path: registry_path,
        key: invalid_key,
        presence: 'NOT_FOUND',
      });
      expectedItems.push({
        hive: test_hive,
        path: invalid_path,
        key: valid_key,
        presence: 'NOT_FOUND',
      });
      for (let i = 0; i < settingsItems.length; ++i) {
        chrome.test.assertEq(settingsItems[i], expectedItems[i]);
      }
      chrome.test.notifyPass();
  )";

  AccountInfo account_info = SignIn("[email protected]");
  RunTest(base::StringPrintf(kTest, account_info.gaia.c_str(), kOptions.c_str(),
                             kAssertions));
}

#endif  // BUILDFLAG(IS_WIN)

#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest,
                       GetRegistrySettings_UnsupportedPlatform) {}

#endif  // !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// TODO(crbug.com/40888560): Failing consistently on Mac.
// TODO(crbug.com/40863616): Flaky on Linux.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#define MAYBE_GetFileSystemInfo_Success
#else
#define MAYBE_GetFileSystemInfo_Success
#endif
IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest,
                       MAYBE_GetFileSystemInfo_Success) {}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_MAC)
// TODO(http://crbug.com/1408618): Failing consistently on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_GetPlistSettings_Success
#else
#define MAYBE_GetPlistSettings_Success
#endif
IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateApiTest,
                       MAYBE_GetPlistSettings_Success) {
  constexpr char kTest[] = R"(
      chrome.test.assertEq(
        'function',
        typeof chrome.enterprise.reportingPrivate.getSettings);
      const userContext = {userId: '%s'};

      const options = [];

      %s

      const request = {userContext, options};

   chrome.enterprise.reportingPrivate.getSettings(
    request,
    (settingItems) => {
        chrome.test.assertNoLastError();

        %s

        chrome.test.notifyPass();
      });
  )";

  std::string extra_items = base::StringPrintf(
      R"(
    const filePath = '%s';
    const validKeyPath = "Key1.SubKey1.SubSubKey1[0][10]";
    const invalidKeyPath = "Key1.SubKey1.SubSubKey1[0][0][3]";
    options.push({
      path: filePath,
      key: validKeyPath,
      getValue: true
    });
    options.push({
      path: filePath,
      key: invalidKeyPath,
      getValue: true
    });
  )",

      device_signals::test::GetMixArrayDictionaryPlistPath().value().c_str());

  constexpr char kAssertions[] = R"(
        chrome.test.assertTrue(settingItems instanceof Array);
        chrome.test.assertEq(2, settingItems.length);
        for (const response of settingItems) {
          chrome.test.assertEq(filePath, response.path);
          if (response.key == validKeyPath) {
            chrome.test.assertEq("FOUND", response.presence);
            chrome.test.assertEq(
              '\"string10\"', response.value);
          } else if (response.key == invalidKeyPath) {
            chrome.test.assertEq("NOT_FOUND", response.presence);
            chrome.test.assertEq(null, response.value);
          } else {
            chrome.test.fail();
          }
        }
  )";

  AccountInfo account_info = SignIn("[email protected]");
  RunTest(base::StringPrintf(kTest, account_info.gaia.c_str(),
                             extra_items.c_str(), kAssertions));
}
#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_CHROMEOS)
static void RunTestUsingProfile(const std::string& background_js,
                                Profile* profile) {
  ResultCatcher result_catcher;
  TestExtensionDir test_dir;
  test_dir.WriteManifest(
      base::StringPrintf(kManifestTemplate, kAuthorizedManifestKey));

  // Since the API functions use async callbacks, this wrapper code is
  // necessary for assertions to work properly.
  constexpr char kTestWrapper[] = R"(
        chrome.test.runTests([
          async function asyncAssertions() {
            %s
          }
        ]);)";
  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
                     base::StringPrintf(kTestWrapper, background_js.c_str()));

  ChromeTestExtensionLoader loader(profile);
  loader.set_ignore_manifest_warnings(true);

  const Extension* extension =
      loader.LoadExtension(test_dir.UnpackedPath()).get();
  ASSERT_TRUE(extension);
  ASSERT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}

static std::string CreateValidRecord() {
  std::vector<uint8_t> serialized_record_data;
  std::string serialized_data = R"({"TEST_KEY":"TEST_VALUE"})";
  reporting::Record record;
  record.set_data(serialized_data);
  record.set_destination(reporting::Destination::TELEMETRY_METRIC);
  record.set_timestamp_us(base::Time::Now().InMillisecondsSinceUnixEpoch() *
                          base::Time::kMicrosecondsPerMillisecond);
  serialized_record_data.resize(record.SerializeAsString().size());
  record.SerializeToArray(serialized_record_data.data(),
                          serialized_record_data.size());

  // Print std::vector<uint8_t> into a form like "[1,2,3,4]"
  std::string serialized_record_data_str = "[";
  for (size_t i = 0; i < serialized_record_data.size(); i++) {
    if (i == serialized_record_data.size() - 1) {
      base::StrAppend(&serialized_record_data_str,
                      {base::NumberToString(serialized_record_data[i]), "]"});
    } else {
      base::StrAppend(&serialized_record_data_str,
                      {base::NumberToString(serialized_record_data[i]), ","});
    }
  }
  return serialized_record_data_str;
}
#endif  // BUILDFLAG(IS_CHROMEOS)

// Inheriting from DevicePolicyCrosBrowserTest enables use of AffiliationMixin
// for setting up profile/device affiliation. Only available in Ash.
#if BUILDFLAG(IS_CHROMEOS_ASH)
struct Params {
  explicit Params(bool affiliated) : affiliated(affiliated) {}
  // Whether the user is expected to be affiliated.
  bool affiliated;
};

class EnterpriseReportingPrivateEnqueueRecordApiTest
    : public ::policy::DevicePolicyCrosBrowserTest,
      public ::testing::WithParamInterface<Params> {
 protected:
  EnterpriseReportingPrivateEnqueueRecordApiTest() {
    affiliation_mixin_.set_affiliated(GetParam().affiliated);
    crypto_home_mixin_.MarkUserAsExisting(affiliation_mixin_.account_id());
  }

  ~EnterpriseReportingPrivateEnqueueRecordApiTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ::policy::AffiliationTestHelper::AppendCommandLineSwitchesForLoginManager(
        command_line);
    ::policy::DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
  }

  ::policy::DevicePolicyCrosTestHelper test_helper_;
  ::policy::AffiliationMixin affiliation_mixin_{&mixin_host_, &test_helper_};
  ash::CryptohomeMixin crypto_home_mixin_{&mixin_host_};
};

IN_PROC_BROWSER_TEST_P(EnterpriseReportingPrivateEnqueueRecordApiTest,
                       PRE_EnqueueRecord) {
  policy::AffiliationTestHelper::PreLoginUser(affiliation_mixin_.account_id());
}

IN_PROC_BROWSER_TEST_P(EnterpriseReportingPrivateEnqueueRecordApiTest,
                       EnqueueRecord) {
  policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());

  constexpr char kTest[] = R"(

        const request = {
          eventType: "USER",
          priority: 4,
          recordData: Uint8Array.from(%s),
        };

        chrome.enterprise.reportingPrivate.enqueueRecord(request, () =>{
          %s
          chrome.test.succeed();
        });

      )";

  std::string javascript_assertion =
      GetParam().affiliated
          ? "chrome.test.assertNoLastError();"
          : base::StrCat({"chrome.test.assertLastError(\'",
                          EnterpriseReportingPrivateEnqueueRecordFunction::
                              kErrorProfileNotAffiliated,
                          "\');"});

  ASSERT_EQ(GetParam().affiliated,
            enterprise_util::IsProfileAffiliated(
                ash::ProfileHelper::Get()->GetProfileByAccountId(
                    affiliation_mixin_.account_id())));

  RunTestUsingProfile(base::StringPrintf(kTest, CreateValidRecord().c_str(),
                                         javascript_assertion.c_str()),
                      ash::ProfileHelper::Get()->GetProfileByAccountId(
                          affiliation_mixin_.account_id()));
}
INSTANTIATE_TEST_SUITE_P(TestAffiliation,
                         EnterpriseReportingPrivateEnqueueRecordApiTest,
                         ::testing::Values(Params(/*affiliated=*/true),
                                           Params(/*affiliated=*/false)));
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS_LACROS)

using EnterpriseReportingPrivateEnqueueRecordApiTest = ExtensionApiTest;

static void SetupAffiliationLacros() {
  constexpr char kDomain[] = "fake-domain";
  constexpr char kFakeProfileClientId[] = "fake-profile-client-id";
  constexpr char kFakeDMToken[] = "fake-dm-token";
  enterprise_management::PolicyData profile_policy_data;
  profile_policy_data.add_user_affiliation_ids(kAffiliationId);
  profile_policy_data.set_managed_by(kDomain);
  profile_policy_data.set_device_id(kFakeProfileClientId);
  profile_policy_data.set_request_token(kFakeDMToken);
  policy::PolicyLoaderLacros::set_main_user_policy_data_for_testing(
      std::move(profile_policy_data));

  crosapi::mojom::BrowserInitParamsPtr init_params =
      crosapi::mojom::BrowserInitParams::New();
  init_params->device_properties = crosapi::mojom::DeviceProperties::New();
  init_params->device_properties->device_dm_token = kFakeDMToken;
  init_params->device_properties->device_affiliation_ids = {kAffiliationId};
  chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params));
}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateEnqueueRecordApiTest,
                       EnqueueRecordFailsWithUnaffiliatedProfile) {
  constexpr char kTest[] = R"(

        const request = {
          eventType: "USER",
          priority: 4,
          recordData: Uint8Array.from(%s),
        };

        chrome.enterprise.reportingPrivate.enqueueRecord(request, () =>{
         chrome.test.assertLastError('%s');

          chrome.test.succeed();
        });

      )";
  const std::string kErrorMsg =
      EnterpriseReportingPrivateEnqueueRecordFunction::
          kErrorProfileNotAffiliated;
  RunTestUsingProfile(
      base::StringPrintf(kTest, CreateValidRecord().c_str(), kErrorMsg.c_str()),
      profile());
}

IN_PROC_BROWSER_TEST_F(EnterpriseReportingPrivateEnqueueRecordApiTest,
                       EnqueueRecordSucceedsWithAffiliatedProfile) {
  SetupAffiliationLacros();
  constexpr char kTest[] = R"(

        const request = {
          eventType: "USER",
          priority: 4,
          recordData: Uint8Array.from(%s),
        };

        chrome.enterprise.reportingPrivate.enqueueRecord(request, () =>{
          chrome.test.assertNoLastError();

          chrome.test.succeed();
        });

      )";
  RunTestUsingProfile(base::StringPrintf(kTest, CreateValidRecord().c_str()),
                      profile());
}
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

}  // namespace extensions