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

#include "extensions/browser/api/device_permissions_manager.h"

#include <stdint.h>

#include <memory>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/values_test_util.h"
#include "chrome/browser/extensions/test_extension_environment.h"
#include "chrome/test/base/testing_profile.h"
#include "extensions/browser/api/hid/hid_device_manager.h"
#include "extensions/browser/api/usb/usb_device_manager.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/extension.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/cpp/test/fake_hid_manager.h"
#include "services/device/public/cpp/test/fake_usb_device_manager.h"
#include "services/device/public/mojom/hid.mojom.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

using device::FakeUsbDeviceInfo;
using device::mojom::HidBusType;
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgPointee;

}  // namespace

class DevicePermissionsManagerTest : public testing::Test {
 protected:
  void SetUp() override {
    testing::Test::SetUp();
    env_ = std::make_unique<extensions::TestExtensionEnvironment>();
    extension_ = env_->MakeExtension(
        base::test::ParseJsonDict("{"
                                  "  \"app\": {"
                                  "    \"background\": {"
                                  "      \"scripts\": [\"background.js\"]"
                                  "    }"
                                  "  },"
                                  "  \"permissions\": [ \"hid\", \"usb\" ]"
                                  "}"));

    // Set fake device manager for extensions::UsbDeviceManager.
    mojo::PendingRemote<device::mojom::UsbDeviceManager> usb_manager;
    fake_usb_manager_.AddReceiver(usb_manager.InitWithNewPipeAndPassReceiver());
    UsbDeviceManager::Get(env_->profile())
        ->SetDeviceManagerForTesting(std::move(usb_manager));
    base::RunLoop().RunUntilIdle();

    device0_ = fake_usb_manager_.CreateAndAddDevice(0, 0, "Test Manufacturer",
                                                    "Test Product", "ABCDE");
    device1_ = fake_usb_manager_.CreateAndAddDevice(0, 0, "Test Manufacturer",
                                                    "Test Product", "");
    device2_ = fake_usb_manager_.CreateAndAddDevice(0, 0, "Test Manufacturer",
                                                    "Test Product", "12345");
    device3_ = fake_usb_manager_.CreateAndAddDevice(0, 0, "Test Manufacturer",
                                                    "Test Product", "");

    HidDeviceManager::OverrideHidManagerBinderForTesting(base::BindRepeating(
        &device::FakeHidManager::Bind, base::Unretained(&fake_hid_manager_)));
    HidDeviceManager::Get(env_->profile())->LazyInitialize();
    base::RunLoop().RunUntilIdle();

    device4_ = fake_hid_manager_.CreateAndAddDevice(
        "4", 0, 0, "Test HID Device", "abcde", HidBusType::kHIDBusTypeUSB);
    device5_ = fake_hid_manager_.CreateAndAddDevice(
        "5", 0, 0, "Test HID Device", "", HidBusType::kHIDBusTypeUSB);
    device6_ = fake_hid_manager_.CreateAndAddDevice(
        "6", 0, 0, "Test HID Device", "67890", HidBusType::kHIDBusTypeUSB);
    device7_ = fake_hid_manager_.CreateAndAddDevice(
        "7", 0, 0, "Test HID Device", "", HidBusType::kHIDBusTypeUSB);
  }

  void TearDown() override {
    extension_ = nullptr;
    env_.reset(nullptr);
  }

  std::unique_ptr<extensions::TestExtensionEnvironment> env_;
  raw_ptr<const extensions::Extension> extension_ = nullptr;
  device::FakeUsbDeviceManager fake_usb_manager_;
  device::mojom::UsbDeviceInfoPtr device0_;
  device::mojom::UsbDeviceInfoPtr device1_;
  device::mojom::UsbDeviceInfoPtr device2_;
  device::mojom::UsbDeviceInfoPtr device3_;

  device::FakeHidManager fake_hid_manager_;
  device::mojom::HidDeviceInfoPtr device4_;
  device::mojom::HidDeviceInfoPtr device5_;
  device::mojom::HidDeviceInfoPtr device6_;
  device::mojom::HidDeviceInfoPtr device7_;
};

TEST_F(DevicePermissionsManagerTest, AllowAndClearDevices) {
  DevicePermissionsManager* manager =
      DevicePermissionsManager::Get(env_->profile());
  manager->AllowUsbDevice(extension_->id(), *device0_);
  manager->AllowUsbDevice(extension_->id(), *device1_);

  DevicePermissions* device_permissions =
      manager->GetForExtension(extension_->id());
  scoped_refptr<DevicePermissionEntry> device0_entry =
      device_permissions->FindUsbDeviceEntry(*device0_);
  ASSERT_TRUE(device0_entry);
  scoped_refptr<DevicePermissionEntry> device1_entry =
      device_permissions->FindUsbDeviceEntry(*device1_);
  ASSERT_TRUE(device1_entry);
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device2_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device3_));
  EXPECT_EQ(2U, device_permissions->entries().size());

  EXPECT_EQ(u"Test Product from Test Manufacturer (serial number ABCDE)",
            device0_entry->GetPermissionMessageString());
  EXPECT_EQ(u"Test Product from Test Manufacturer",
            device1_entry->GetPermissionMessageString());

  manager->Clear(extension_->id());
  // The device_permissions object is deleted by Clear.
  device_permissions = manager->GetForExtension(extension_->id());

  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device1_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device2_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device3_));
  EXPECT_EQ(0U, device_permissions->entries().size());

  // After clearing device it should be possible to grant permission again.
  manager->AllowUsbDevice(extension_->id(), *device0_);
  manager->AllowUsbDevice(extension_->id(), *device1_);

  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device1_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device2_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device3_));
}

TEST_F(DevicePermissionsManagerTest, DisconnectDevice) {
  DevicePermissionsManager* manager =
      DevicePermissionsManager::Get(env_->profile());
  manager->AllowUsbDevice(extension_->id(), *device0_);
  manager->AllowUsbDevice(extension_->id(), *device1_);

  DevicePermissions* device_permissions =
      manager->GetForExtension(extension_->id());
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device1_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device2_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device3_));

  fake_usb_manager_.RemoveDevice(device0_->guid);
  fake_usb_manager_.RemoveDevice(device1_->guid);

  base::RunLoop().RunUntilIdle();

  // Device 0 will be accessible when it is reconnected because it can be
  // recognized by its serial number.
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device0_));
  // Device 1 does not have a serial number and cannot be distinguished from
  // any other device of the same model so the app must request permission again
  // when it is reconnected.
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device1_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device2_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device3_));
}

TEST_F(DevicePermissionsManagerTest, RevokeAndRegrantAccess) {
  DevicePermissionsManager* manager =
      DevicePermissionsManager::Get(env_->profile());
  manager->AllowUsbDevice(extension_->id(), *device0_);
  manager->AllowUsbDevice(extension_->id(), *device1_);

  DevicePermissions* device_permissions =
      manager->GetForExtension(extension_->id());
  scoped_refptr<DevicePermissionEntry> device0_entry =
      device_permissions->FindUsbDeviceEntry(*device0_);
  ASSERT_TRUE(device0_entry);
  scoped_refptr<DevicePermissionEntry> device1_entry =
      device_permissions->FindUsbDeviceEntry(*device1_);
  ASSERT_TRUE(device1_entry);

  manager->RemoveEntry(extension_->id(), device0_entry);
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device1_));

  manager->AllowUsbDevice(extension_->id(), *device0_);
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device1_));

  manager->RemoveEntry(extension_->id(), device1_entry);
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device1_));

  manager->AllowUsbDevice(extension_->id(), *device1_);
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device0_));
  EXPECT_TRUE(device_permissions->FindUsbDeviceEntry(*device1_));
}

TEST_F(DevicePermissionsManagerTest, UpdateLastUsed) {
  DevicePermissionsManager* manager =
      DevicePermissionsManager::Get(env_->profile());
  manager->AllowUsbDevice(extension_->id(), *device0_);

  DevicePermissions* device_permissions =
      manager->GetForExtension(extension_->id());
  scoped_refptr<DevicePermissionEntry> device0_entry =
      device_permissions->FindUsbDeviceEntry(*device0_);
  EXPECT_TRUE(device0_entry->last_used().is_null());

  manager->UpdateLastUsed(extension_->id(), device0_entry);
  EXPECT_FALSE(device0_entry->last_used().is_null());
}

TEST_F(DevicePermissionsManagerTest, LoadPrefs) {
  base::Value prefs_value = base::test::ParseJson(
      "["
      "  {"
      "    \"manufacturer_string\": \"Test Manufacturer\","
      "    \"product_id\": 0,"
      "    \"product_string\": \"Test Product\","
      "    \"serial_number\": \"ABCDE\","
      "    \"type\": \"usb\","
      "    \"vendor_id\": 0"
      "  },"
      "  {"
      "    \"product_id\": 0,"
      "    \"product_string\": \"Test HID Device\","
      "    \"serial_number\": \"abcde\","
      "    \"type\": \"hid\","
      "    \"vendor_id\": 0"
      "  }"
      "]");
  env_->GetExtensionPrefs()->UpdateExtensionPref(extension_->id(), "devices",
                                                 std::move(prefs_value));

  DevicePermissionsManager* manager =
      DevicePermissionsManager::Get(env_->profile());
  DevicePermissions* device_permissions =
      manager->GetForExtension(extension_->id());
  scoped_refptr<DevicePermissionEntry> device0_entry =
      device_permissions->FindUsbDeviceEntry(*device0_);
  ASSERT_TRUE(device0_entry);
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device1_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device2_));
  EXPECT_FALSE(device_permissions->FindUsbDeviceEntry(*device3_));
  scoped_refptr<DevicePermissionEntry> device4_entry =
      device_permissions->FindHidDeviceEntry(*device4_);
  ASSERT_TRUE(device4_entry);
  EXPECT_FALSE(device_permissions->FindHidDeviceEntry(*device5_));
  EXPECT_FALSE(device_permissions->FindHidDeviceEntry(*device6_));
  EXPECT_FALSE(device_permissions->FindHidDeviceEntry(*device7_));

  EXPECT_EQ(u"Test Product from Test Manufacturer (serial number ABCDE)",
            device0_entry->GetPermissionMessageString());
  EXPECT_EQ(u"Test HID Device (serial number abcde)",
            device4_entry->GetPermissionMessageString());
}

TEST_F(DevicePermissionsManagerTest, PermissionMessages) {
  std::u16string empty;
  std::u16string product(u"Widget");
  std::u16string manufacturer(u"ACME");
  std::u16string serial_number(u"A");

  EXPECT_EQ(u"Unknown product 0001 from vendor 0000",
            DevicePermissionsManager::GetPermissionMessage(
                0x0000, 0x0001, empty, empty, empty, false));

  EXPECT_EQ(u"Unknown product 0001 from vendor 0000 (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x0000, 0x0001, empty, empty, u"A", false));

  EXPECT_EQ(u"Unknown product 0010 from Google Inc.",
            DevicePermissionsManager::GetPermissionMessage(
                0x18D1, 0x0010, empty, empty, empty, false));

  EXPECT_EQ(u"Unknown product 0010 from Google Inc. (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x18D1, 0x0010, empty, empty, serial_number, false));

  EXPECT_EQ(u"Nexus One from Google Inc.",
            DevicePermissionsManager::GetPermissionMessage(
                0x18D1, 0x4E11, empty, empty, empty, true));

  EXPECT_EQ(u"Nexus One from Google Inc. (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x18D1, 0x4E11, empty, empty, serial_number, true));

  EXPECT_EQ(u"Nexus One", DevicePermissionsManager::GetPermissionMessage(
                              0x18D1, 0x4E11, empty, empty, empty, false));

  EXPECT_EQ(u"Nexus One (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x18D1, 0x4E11, empty, empty, serial_number, false));

  EXPECT_EQ(u"Unknown product 0001 from ACME",
            DevicePermissionsManager::GetPermissionMessage(
                0x0000, 0x0001, manufacturer, empty, empty, false));

  EXPECT_EQ(u"Unknown product 0001 from ACME (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x0000, 0x0001, manufacturer, empty, serial_number, false));

  EXPECT_EQ(u"Widget from ACME",
            DevicePermissionsManager::GetPermissionMessage(
                0x0001, 0x0000, manufacturer, product, empty, true));

  EXPECT_EQ(u"Widget from ACME (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x0001, 0x0000, manufacturer, product, serial_number, true));

  EXPECT_EQ(u"Widget",
            DevicePermissionsManager::GetPermissionMessage(
                0x0001, 0x0000, manufacturer, product, empty, false));

  EXPECT_EQ(u"Widget (serial number A)",
            DevicePermissionsManager::GetPermissionMessage(
                0x0001, 0x0000, manufacturer, product, serial_number, false));
}

}  // namespace extensions
