// 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 <vector>

#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/extensions/test_extension_environment.h"
#include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
#include "chrome/test/base/testing_profile.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/permissions/permissions_test_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/permissions_info.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {

namespace {

using extensions::PermissionSet;
using extensions::PermissionsParser;
using extensions::mojom::ManifestLocation;

constexpr char kChromeOSSystemExtensionId[] =
    "gogonhoemckpdpadfnjnpgbjpbjnodgc";
constexpr char16_t kDiagnosticsPermissionMessage[] =
    u"Run ChromeOS diagnostic tests";
constexpr char16_t kTelemetryEventsPermissionMessage[] =
    u"Subscribe to ChromeOS system events";
constexpr char16_t kTelemetryPermissionMessage[] =
    u"Read ChromeOS device information and data";
constexpr char16_t kTelemetrySerialNumberPermissionMessage[] =
    u"Read ChromeOS device and component serial numbers";
constexpr char16_t kTelemetryNetworkInformationPermissionMessage[] =
    u"Read ChromeOS network information";
constexpr char16_t kAttachedDeviceInfo[] =
    u"Read attached devices information and data";
constexpr char16_t kBluetoothPeripheralsInfo[] =
    u"Read Bluetooth peripherals information and data";
constexpr char16_t kManagementAudio[] = u"Manage ChromeOS audio settings";
constexpr char16_t kDiagnosticsNetworkInfoForMlab[] =
    u"Collect IP address and network measurement results for Measurement Lab, "
    u"according to their privacy policy (measurementlab.net/privacy)";
}  // namespace

// Tests that ChromePermissionMessageProvider provides not only correct, but
// meaningful permission messages that coalesce correctly where appropriate.
// The following tests target "chromeos_system_extension" related permissions
// (e.g. os.telemetry)
class ChromeOSPermissionMessageUnittest : public testing::Test {
 public:
  ChromeOSPermissionMessageUnittest()
      : message_provider_(new extensions::ChromePermissionMessageProvider()) {}
  ChromeOSPermissionMessageUnittest(const ChromeOSPermissionMessageUnittest&) =
      delete;
  ChromeOSPermissionMessageUnittest& operator=(
      const ChromeOSPermissionMessageUnittest&) = delete;
  ~ChromeOSPermissionMessageUnittest() override = default;

 protected:
  void CreateAndInstallExtensionWithPermissions(
      base::ListValue required_permissions,
      base::ListValue optional_permissions) {
    app_ = extensions::ExtensionBuilder("Test ChromeOS System Extension")
               .SetManifestKey("chromeos_system_extension", base::DictValue())
               .SetManifestKey("permissions", std::move(required_permissions))
               .SetManifestKey("optional_permissions",
                               std::move(optional_permissions))
               .SetManifestKey(
                   "externally_connectable",
                   base::DictValue().Set(
                       "matches",
                       base::ListValue().Append(
                           "*://googlechromelabs.github.io/"
                           "cros-sample-telemetry-extension/test-page/*")))
               .SetID(kChromeOSSystemExtensionId)  // only allowlisted id
               .SetLocation(ManifestLocation::kInternal)
               .Build();

    env_.GetExtensionRegistrar()->AddExtension(app_.get());
  }

  // Returns the permission messages that would display in the prompt that
  // requests all the optional permissions for the current |app_|.
  std::vector<std::u16string> GetInactiveOptionalPermissionMessages() {
    std::unique_ptr<const PermissionSet> granted_permissions =
        env_.GetExtensionPrefs()->GetGrantedPermissions(app_->id());
    const PermissionSet& optional_permissions =
        PermissionsParser::GetOptionalPermissions(app_.get());
    std::unique_ptr<const PermissionSet> requested_permissions =
        PermissionSet::CreateDifference(optional_permissions,
                                        *granted_permissions);
    return GetMessages(*requested_permissions);
  }

  void GrantOptionalPermissions() {
    extensions::permissions_test_util::
        GrantOptionalPermissionsAndWaitForCompletion(
            env_.profile(), *app_,
            PermissionsParser::GetOptionalPermissions(app_.get()));
  }

  std::vector<std::u16string> active_permissions() {
    return GetMessages(app_->permissions_data()->active_permissions());
  }

  std::vector<std::u16string> required_permissions() {
    return GetMessages(PermissionsParser::GetRequiredPermissions(app_.get()));
  }

  std::vector<std::u16string> optional_permissions() {
    return GetMessages(PermissionsParser::GetOptionalPermissions(app_.get()));
  }

 private:
  std::vector<std::u16string> GetMessages(const PermissionSet& permissions) {
    std::vector<std::u16string> messages;
    for (const extensions::PermissionMessage& msg :
         message_provider_->GetPermissionMessages(
             message_provider_->GetAllPermissionIDs(permissions,
                                                    app_->GetType()))) {
      messages.push_back(msg.message());
    }
    return messages;
  }

  extensions::TestExtensionEnvironment env_;
  std::unique_ptr<extensions::ChromePermissionMessageProvider>
      message_provider_;
  scoped_refptr<const extensions::Extension> app_;
};

TEST_F(ChromeOSPermissionMessageUnittest, OsAttachedDeviceInfo) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(), base::ListValue().Append("os.attached_device_info"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kAttachedDeviceInfo, optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kAttachedDeviceInfo, GetInactiveOptionalPermissionMessages()[0]);
  EXPECT_EQ(0U, required_permissions().size());
  EXPECT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  EXPECT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kAttachedDeviceInfo, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsBluetoothPeripheralsInfo) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(),
      base::ListValue().Append("os.bluetooth_peripherals_info"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kBluetoothPeripheralsInfo, optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kBluetoothPeripheralsInfo,
            GetInactiveOptionalPermissionMessages()[0]);
  EXPECT_EQ(0U, required_permissions().size());
  EXPECT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  EXPECT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kBluetoothPeripheralsInfo, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsDiagnosticsMessage) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue().Append("os.diagnostics"), base::ListValue());

  ASSERT_EQ(0U, optional_permissions().size());
  ASSERT_EQ(1U, required_permissions().size());
  EXPECT_EQ(kDiagnosticsPermissionMessage, required_permissions()[0]);
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kDiagnosticsPermissionMessage, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsDiagnosticsNetworkInfoForMlab) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(),
      base::ListValue().Append("os.diagnostics.network_info_mlab"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kDiagnosticsNetworkInfoForMlab, optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kDiagnosticsNetworkInfoForMlab,
            GetInactiveOptionalPermissionMessages()[0]);
  EXPECT_EQ(0U, required_permissions().size());
  EXPECT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  EXPECT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kDiagnosticsNetworkInfoForMlab, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetryMessage) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue().Append("os.telemetry"), base::ListValue());

  ASSERT_EQ(0U, optional_permissions().size());
  ASSERT_EQ(1U, required_permissions().size());
  EXPECT_EQ(kTelemetryPermissionMessage, required_permissions()[0]);
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kTelemetryPermissionMessage, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetrySerialNumber) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(),
      base::ListValue().Append("os.telemetry.serial_number"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kTelemetrySerialNumberPermissionMessage, optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kTelemetrySerialNumberPermissionMessage,
            GetInactiveOptionalPermissionMessages()[0]);
  ASSERT_EQ(0U, required_permissions().size());
  ASSERT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  ASSERT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kTelemetrySerialNumberPermissionMessage, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetryNetworkInformation) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(), base::ListValue().Append("os.telemetry.network_info"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kTelemetryNetworkInformationPermissionMessage,
            optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kTelemetryNetworkInformationPermissionMessage,
            GetInactiveOptionalPermissionMessages()[0]);
  ASSERT_EQ(0U, required_permissions().size());
  ASSERT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  ASSERT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kTelemetryNetworkInformationPermissionMessage,
            active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetryEventsMessage) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(), base::ListValue().Append("os.events"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kTelemetryEventsPermissionMessage, optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kTelemetryEventsPermissionMessage,
            GetInactiveOptionalPermissionMessages()[0]);
  EXPECT_EQ(0U, required_permissions().size());
  EXPECT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  EXPECT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kTelemetryEventsPermissionMessage, active_permissions()[0]);
}

TEST_F(ChromeOSPermissionMessageUnittest, OsManagementAudio) {
  CreateAndInstallExtensionWithPermissions(
      base::ListValue(), base::ListValue().Append("os.management.audio"));

  ASSERT_EQ(1U, optional_permissions().size());
  EXPECT_EQ(kManagementAudio, optional_permissions()[0]);
  ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size());
  EXPECT_EQ(kManagementAudio, GetInactiveOptionalPermissionMessages()[0]);
  EXPECT_EQ(0U, required_permissions().size());
  EXPECT_EQ(0U, active_permissions().size());

  GrantOptionalPermissions();

  EXPECT_EQ(0U, GetInactiveOptionalPermissionMessages().size());
  ASSERT_EQ(1U, active_permissions().size());
  EXPECT_EQ(kManagementAudio, active_permissions()[0]);
}

}  // namespace chromeos
