// 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 "chrome/browser/serial/serial_policy_allowed_ports.h"

#include "base/test/values_test_util.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace {

using ::base::test::ParseJson;
using ::testing::UnorderedElementsAre;

device::mojom::SerialPortInfoPtr CreateUsbDevice(uint16_t vendor_id,
                                                 uint16_t product_id) {
  auto port = device::mojom::SerialPortInfo::New();
  port->token = base::UnguessableToken::Create();
  port->has_vendor_id = true;
  port->vendor_id = vendor_id;
  port->has_product_id = true;
  port->product_id = product_id;
  return port;
}

device::mojom::SerialPortInfoPtr CreatePlatformPort() {
  auto port = device::mojom::SerialPortInfo::New();
  port->token = base::UnguessableToken::Create();
  return port;
}

}  // namespace

class SerialPolicyAllowedPortsTest : public testing::Test {
 public:
  SerialPolicyAllowedPortsTest() {
    RegisterLocalState(local_state_.registry());
  }

  ~SerialPolicyAllowedPortsTest() override = default;

  void SetAllowAllPortsForUrlsPrefValue(const base::Value& value) {
    local_state_.Set(prefs::kManagedSerialAllowAllPortsForUrls, value);
  }

  void SetAllowUsbDevicesForUrlsPrefValue(const base::Value& value) {
    local_state_.Set(prefs::kManagedSerialAllowUsbDevicesForUrls, value);
  }

  void InitializePolicy() {
    EXPECT_FALSE(policy_);
    policy_ = std::make_unique<SerialPolicyAllowedPorts>(&local_state_);
  }

 protected:
  SerialPolicyAllowedPorts* policy() { return policy_.get(); }

 private:
  content::BrowserTaskEnvironment task_environment_;
  TestingPrefServiceSimple local_state_;
  std::unique_ptr<SerialPolicyAllowedPorts> policy_;
};

TEST_F(SerialPolicyAllowedPortsTest, InitializeWithMissingPrefValue) {
  InitializePolicy();
  EXPECT_TRUE(policy()->usb_device_policy().empty());
  EXPECT_TRUE(policy()->usb_vendor_policy().empty());
  EXPECT_TRUE(policy()->all_ports_policy().empty());
}

TEST_F(SerialPolicyAllowedPortsTest, InitializeWithEmptyPrefValues) {
  SetAllowAllPortsForUrlsPrefValue(base::Value(base::Value::Type::LIST));
  SetAllowUsbDevicesForUrlsPrefValue(base::Value(base::Value::Type::LIST));

  InitializePolicy();
  EXPECT_TRUE(policy()->usb_device_policy().empty());
  EXPECT_TRUE(policy()->usb_vendor_policy().empty());
  EXPECT_TRUE(policy()->all_ports_policy().empty());
}

TEST_F(SerialPolicyAllowedPortsTest, InitializeWithPrefValues) {
  constexpr char kAllPortsPolicySetting[] = R"(["https://www.youtube.com"])";
  constexpr char kUsbDevicesPolicySetting[] = R"(
    [
      {
        "devices": [
          { "vendor_id": 1234, "product_id": 5678 },
          { "vendor_id": 4321 }
        ],
        "urls": [
          "https://google.com",
          "https://crbug.com"
        ]
      }
    ])";
  const auto kYoutubeOrigin =
      url::Origin::Create(GURL("https://www.youtube.com"));
  const auto kGoogleOrigin = url::Origin::Create(GURL("https://google.com"));
  const auto kCrbugOrigin = url::Origin::Create(GURL("https://crbug.com"));

  SetAllowAllPortsForUrlsPrefValue(ParseJson(kAllPortsPolicySetting));
  SetAllowUsbDevicesForUrlsPrefValue(ParseJson(kUsbDevicesPolicySetting));
  InitializePolicy();

  EXPECT_EQ(1u, policy()->usb_device_policy().size());
  EXPECT_EQ(1u, policy()->usb_vendor_policy().size());
  EXPECT_EQ(1u, policy()->all_ports_policy().size());

  EXPECT_THAT(policy()->all_ports_policy(),
              UnorderedElementsAre(kYoutubeOrigin));

  const auto device_key = std::make_pair(1234, 5678);
  ASSERT_TRUE(policy()->usb_device_policy().contains(device_key));
  EXPECT_THAT(policy()->usb_device_policy().at(device_key),
              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));

  ASSERT_TRUE(policy()->usb_vendor_policy().contains(4321));
  EXPECT_THAT(policy()->usb_vendor_policy().at(4321),
              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));

  auto vendor1_device = CreateUsbDevice(1234, 5678);
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor1_device));
  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor1_device));
  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor1_device));

  auto vendor1_alternate_device = CreateUsbDevice(1234, 1234);
  EXPECT_TRUE(
      policy()->HasPortPermission(kYoutubeOrigin, *vendor1_alternate_device));
  EXPECT_FALSE(
      policy()->HasPortPermission(kGoogleOrigin, *vendor1_alternate_device));
  EXPECT_FALSE(
      policy()->HasPortPermission(kCrbugOrigin, *vendor1_alternate_device));

  auto vendor2_device = CreateUsbDevice(4321, 1234);
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor2_device));
  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor2_device));
  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor2_device));

  auto platform_port = CreatePlatformPort();
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *platform_port));
  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *platform_port));
  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *platform_port));
}

TEST_F(SerialPolicyAllowedPortsTest,
       InitializeWithMissingPrefValuesThenUpdate) {
  InitializePolicy();

  constexpr char kAllPortsPolicySetting[] = R"(["https://www.youtube.com"])";
  constexpr char kUsbDevicesPolicySetting[] = R"(
    [
      {
        "devices": [
          { "vendor_id": 1234, "product_id": 5678 },
          { "vendor_id": 4321 }
        ],
        "urls": [
          "https://google.com",
          "https://crbug.com"
        ]
      }
    ])";
  const auto kYoutubeOrigin =
      url::Origin::Create(GURL("https://www.youtube.com"));
  const auto kGoogleOrigin = url::Origin::Create(GURL("https://google.com"));
  const auto kCrbugOrigin = url::Origin::Create(GURL("https://crbug.com"));

  SetAllowAllPortsForUrlsPrefValue(ParseJson(kAllPortsPolicySetting));
  SetAllowUsbDevicesForUrlsPrefValue(ParseJson(kUsbDevicesPolicySetting));

  EXPECT_EQ(1u, policy()->usb_device_policy().size());
  EXPECT_EQ(1u, policy()->usb_vendor_policy().size());
  EXPECT_EQ(1u, policy()->all_ports_policy().size());

  EXPECT_THAT(policy()->all_ports_policy(),
              UnorderedElementsAre(kYoutubeOrigin));

  const auto device_key = std::make_pair(1234, 5678);
  ASSERT_TRUE(policy()->usb_device_policy().contains(device_key));
  EXPECT_THAT(policy()->usb_device_policy().at(device_key),
              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));

  ASSERT_TRUE(policy()->usb_vendor_policy().contains(4321));
  EXPECT_THAT(policy()->usb_vendor_policy().at(4321),
              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));

  auto vendor1_device = CreateUsbDevice(1234, 5678);
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor1_device));
  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor1_device));
  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor1_device));

  auto vendor1_alternate_device = CreateUsbDevice(1234, 1234);
  EXPECT_TRUE(
      policy()->HasPortPermission(kYoutubeOrigin, *vendor1_alternate_device));
  EXPECT_FALSE(
      policy()->HasPortPermission(kGoogleOrigin, *vendor1_alternate_device));
  EXPECT_FALSE(
      policy()->HasPortPermission(kCrbugOrigin, *vendor1_alternate_device));

  auto vendor2_device = CreateUsbDevice(4321, 1234);
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor2_device));
  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor2_device));
  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor2_device));

  auto platform_port = CreatePlatformPort();
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *platform_port));
  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *platform_port));
  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *platform_port));
}

TEST_F(SerialPolicyAllowedPortsTest, InitializeWithPrefValuesThenRemovePolicy) {
  constexpr char kAllPortsPolicySetting[] = R"(["https://www.youtube.com"])";
  constexpr char kUsbDevicesPolicySetting[] = R"(
    [
      {
        "devices": [
          { "vendor_id": 1234, "product_id": 5678 },
          { "vendor_id": 4321 }
        ],
        "urls": [
          "https://google.com",
          "https://crbug.com"
        ]
      }
    ])";

  SetAllowAllPortsForUrlsPrefValue(ParseJson(kAllPortsPolicySetting));
  SetAllowUsbDevicesForUrlsPrefValue(ParseJson(kUsbDevicesPolicySetting));
  InitializePolicy();

  SetAllowAllPortsForUrlsPrefValue(base::Value(base::Value::Type::LIST));
  SetAllowUsbDevicesForUrlsPrefValue(base::Value(base::Value::Type::LIST));
  EXPECT_TRUE(policy()->usb_device_policy().empty());
  EXPECT_TRUE(policy()->usb_vendor_policy().empty());
  EXPECT_TRUE(policy()->all_ports_policy().empty());
}

TEST_F(SerialPolicyAllowedPortsTest, MultipleItemsWithOverlap) {
  constexpr char kUsbDevicesPolicySetting[] = R"(
    [
      {
        "devices": [
          { "vendor_id": 1234, "product_id": 5678 },
          { "vendor_id": 4321 }
        ],
        "urls": [
          "https://google.com",
          "https://crbug.com"
        ]
      },
      {
        "devices": [
          { "vendor_id": 1234 },
          { "vendor_id": 4321, "product_id": 8765 }
        ],
        "urls": [
          "https://crbug.com",
          "https://www.youtube.com"
        ]
      }
    ])";
  const auto kGoogleOrigin = url::Origin::Create(GURL("https://google.com"));
  const auto kCrbugOrigin = url::Origin::Create(GURL("https://crbug.com"));
  const auto kYoutubeOrigin =
      url::Origin::Create(GURL("https://www.youtube.com"));

  SetAllowUsbDevicesForUrlsPrefValue(ParseJson(kUsbDevicesPolicySetting));
  InitializePolicy();

  EXPECT_EQ(2u, policy()->usb_device_policy().size());
  EXPECT_EQ(2u, policy()->usb_vendor_policy().size());
  EXPECT_EQ(0u, policy()->all_ports_policy().size());

  const auto device1_key = std::make_pair(1234, 5678);
  ASSERT_TRUE(policy()->usb_device_policy().contains(device1_key));
  EXPECT_THAT(policy()->usb_device_policy().at(device1_key),
              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));

  const auto device2_key = std::make_pair(4321, 8765);
  ASSERT_TRUE(policy()->usb_device_policy().contains(device2_key));
  EXPECT_THAT(policy()->usb_device_policy().at(device2_key),
              UnorderedElementsAre(kCrbugOrigin, kYoutubeOrigin));

  ASSERT_TRUE(policy()->usb_vendor_policy().contains(1234));
  EXPECT_THAT(policy()->usb_vendor_policy().at(1234),
              UnorderedElementsAre(kCrbugOrigin, kYoutubeOrigin));

  ASSERT_TRUE(policy()->usb_vendor_policy().contains(4321));
  EXPECT_THAT(policy()->usb_vendor_policy().at(4321),
              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));

  auto vendor1_device1 = CreateUsbDevice(1234, 5678);
  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor1_device1));
  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor1_device1));
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor1_device1));

  auto vendor1_alternate_device = CreateUsbDevice(1234, 1234);
  EXPECT_FALSE(
      policy()->HasPortPermission(kGoogleOrigin, *vendor1_alternate_device));
  EXPECT_TRUE(
      policy()->HasPortPermission(kCrbugOrigin, *vendor1_alternate_device));
  EXPECT_TRUE(
      policy()->HasPortPermission(kYoutubeOrigin, *vendor1_alternate_device));

  auto vendor2_device = CreateUsbDevice(4321, 8765);
  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor2_device));
  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor2_device));
  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor2_device));

  auto vendor2_alternate_device = CreateUsbDevice(4321, 1234);
  EXPECT_TRUE(
      policy()->HasPortPermission(kGoogleOrigin, *vendor2_alternate_device));
  EXPECT_TRUE(
      policy()->HasPortPermission(kCrbugOrigin, *vendor2_alternate_device));
  EXPECT_FALSE(
      policy()->HasPortPermission(kYoutubeOrigin, *vendor2_alternate_device));

  auto vendor3_device = CreateUsbDevice(9012, 3456);
  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *vendor3_device));
  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *vendor3_device));
  EXPECT_FALSE(policy()->HasPortPermission(kYoutubeOrigin, *vendor3_device));

  auto platform_port = CreatePlatformPort();
  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *platform_port));
  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *platform_port));
  EXPECT_FALSE(policy()->HasPortPermission(kYoutubeOrigin, *platform_port));
}
