Commit 2919ec5f authored by Reilly Grant's avatar Reilly Grant Committed by Chromium LUCI CQ

serial: Implement blocklist for USB devices

This change implements a blocklist for USB serial devices similar to the
one used for the WebUSB API. The implementation is kept separate because
the set of filterable device properties will be different for serial
ports even though the current implementation only supports matching USB
devices.

Devices on the blocklist will not appear in the device chooser prompt
and previously granted permissions will be ignored.

Bug: 884928
Change-Id: I4397452aad0738bc80b31c82460f3f37b5fc6324
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2613792
Auto-Submit: Reilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarMatt Reynolds <mattreynolds@chromium.org>
Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#841321}
parent 3d2ee758
......@@ -3936,6 +3936,8 @@ static_library("browser") {
"send_tab_to_self/send_tab_to_self_desktop_util.h",
"serial/chrome_serial_delegate.cc",
"serial/chrome_serial_delegate.h",
"serial/serial_blocklist.cc",
"serial/serial_blocklist.h",
"serial/serial_chooser_context.cc",
"serial/serial_chooser_context.h",
"serial/serial_chooser_context_factory.cc",
......
......@@ -3,8 +3,10 @@
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "chrome/browser/serial/serial_chooser_context.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/ui/browser.h"
......@@ -45,10 +47,29 @@ class SerialTest : public InProcessBrowserTest {
ui_test_utils::NavigateToURL(browser(), url);
}
void TearDown() override {
// Because SerialBlocklist is a singleton it must be cleared after tests run
// to prevent leakage between tests.
feature_list_.Reset();
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
void SetDynamicBlocklist(base::StringPiece value) {
feature_list_.Reset();
std::map<std::string, std::string> parameters;
parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
feature_list_.InitWithFeaturesAndParameters(
{{kWebSerialBlocklist, parameters}}, {});
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
device::FakeSerialPortManager& port_manager() { return port_manager_; }
SerialChooserContext* context() { return context_; }
private:
base::test::ScopedFeatureList feature_list_;
device::FakeSerialPortManager port_manager_;
SerialChooserContext* context_;
};
......@@ -108,4 +129,53 @@ IN_PROC_BROWSER_TEST_F(SerialTest, RemovePort) {
EXPECT_EQ(true, content::EvalJs(web_contents, "removedPromise"));
}
class SerialBlocklistTest : public SerialTest {
public:
void SetUp() override {
// Add a single device to the blocklist. This has to happen before
// BrowserTestBase::SetUp() is run.
std::map<std::string, std::string> parameters;
parameters[kWebSerialBlocklistAdditions.name] = "usb:18D1:58F0";
feature_list_.InitWithFeaturesAndParameters(
{{kWebSerialBlocklist, parameters}}, {});
SerialTest::SetUp();
}
void TearDown() override {
// Because SerialBlocklist is a singleton it must be cleared after tests run
// to prevent leakage between tests.
feature_list_.Reset();
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
SerialTest::TearDown();
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SerialBlocklistTest, Blocklist) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create port and grant permission to it.
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->has_vendor_id = true;
port->vendor_id = 0x18D1;
port->has_product_id = true;
port->product_id = 0x58F0;
url::Origin origin = web_contents->GetMainFrame()->GetLastCommittedOrigin();
context()->GrantPortPermission(origin, origin, *port);
port_manager().AddPort(port.Clone());
// Adding a USB device to the blocklist overrides any previously granted
// permissions.
EXPECT_EQ(0, content::EvalJs(web_contents, R"((async () => {
let ports = await navigator.serial.getPorts();
return ports.length;
})())"));
}
} // namespace
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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_blocklist.h"
#include <algorithm>
#include <string>
#include <tuple>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "services/device/public/mojom/serial.mojom.h"
namespace {
// Returns true if the passed string is exactly 4 digits long and only contains
// valid hexadecimal characters (no leading 0x).
bool IsHexComponent(base::StringPiece string) {
if (string.length() != 4)
return false;
// This is necessary because base::HexStringToUInt allows whitespace and the
// "0x" prefix in its input.
for (char c : string) {
if (c >= '0' && c <= '9')
continue;
if (c >= 'a' && c <= 'f')
continue;
if (c >= 'A' && c <= 'F')
continue;
return false;
}
return true;
}
bool CompareEntry(const SerialBlocklist::Entry& a,
const SerialBlocklist::Entry& b) {
return std::tie(a.usb_vendor_id, a.usb_product_id) <
std::tie(b.usb_vendor_id, b.usb_product_id);
}
// Returns true if an entry in [begin, end) matches the vendor and product IDs
// of |entry| and has a device version greater than or equal to |entry|.
template <class Iterator>
bool EntryMatches(Iterator begin,
Iterator end,
const SerialBlocklist::Entry& entry) {
auto it = std::lower_bound(begin, end, entry, CompareEntry);
return it != end && it->usb_vendor_id == entry.usb_vendor_id &&
it->usb_product_id == entry.usb_product_id;
}
// This list must be sorted according to CompareEntry.
constexpr SerialBlocklist::Entry kStaticEntries[] = {
{0x18D1, 0x58F3}, // Test entry: GOOGLE_HID_ECHO_GADGET
};
} // namespace
constexpr base::Feature kWebSerialBlocklist{"WebSerialBlocklist",
base::FEATURE_ENABLED_BY_DEFAULT};
constexpr base::FeatureParam<std::string> kWebSerialBlocklistAdditions{
&kWebSerialBlocklist, "BlocklistAdditions", /*default_value=*/""};
SerialBlocklist::~SerialBlocklist() = default;
// static
SerialBlocklist& SerialBlocklist::Get() {
static base::NoDestructor<SerialBlocklist> blocklist;
return *blocklist;
}
bool SerialBlocklist::IsExcluded(
const device::mojom::SerialPortInfo& port_info) const {
// Only USB devices can be matched.
if (!port_info.has_vendor_id || !port_info.has_product_id) {
return false;
}
Entry entry(port_info.vendor_id, port_info.product_id);
return EntryMatches(std::begin(kStaticEntries), std::end(kStaticEntries),
entry) ||
EntryMatches(dynamic_entries_.begin(), dynamic_entries_.end(), entry);
}
void SerialBlocklist::ResetToDefaultValuesForTesting() {
dynamic_entries_.clear();
PopulateWithServerProvidedValues();
}
SerialBlocklist::SerialBlocklist() {
DCHECK(std::is_sorted(std::begin(kStaticEntries), std::end(kStaticEntries),
CompareEntry));
PopulateWithServerProvidedValues();
}
void SerialBlocklist::PopulateWithServerProvidedValues() {
std::string blocklist_string = kWebSerialBlocklistAdditions.Get();
for (const auto& entry :
base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
std::vector<base::StringPiece> components = base::SplitStringPiece(
entry, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (components.size() != 3 || components[0] != "usb" ||
!IsHexComponent(components[1]) || !IsHexComponent(components[2])) {
continue;
}
uint32_t vendor_id;
uint32_t product_id;
if (!base::HexStringToUInt(components[1], &vendor_id) ||
!base::HexStringToUInt(components[2], &product_id)) {
continue;
}
dynamic_entries_.emplace_back(vendor_id, product_id);
}
std::sort(dynamic_entries_.begin(), dynamic_entries_.end(), CompareEntry);
}
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SERIAL_SERIAL_BLOCKLIST_H_
#define CHROME_BROWSER_SERIAL_SERIAL_BLOCKLIST_H_
#include <stdint.h>
#include <string>
#include <vector>
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "services/device/public/mojom/serial.mojom-forward.h"
// Feature used to configure entries in the Web Serial API blocklist which can
// be deployed using a server configuration.
extern const base::Feature kWebSerialBlocklist;
// Dynamic additions to the Web Serial API device blocklist.
//
// The string must be a comma-separated list of entries which start with a type
// identifier. The only currently supported type identifier is "usb:". Entries
// may be separated by an arbitrary amount of whitespace.
//
// A USB entry provides a vendor ID and product ID, each a 16-bit integer
// written as exactly 4 hexadecimal digits. For example, the entry
// "usb:1000:001C" matches a device with a vendor ID of 0x1000 and a product
// ID of 0x001C.
//
// Invalid entries in the list will be ignored.
extern const base::FeatureParam<std::string> kWebSerialBlocklistAdditions;
class SerialBlocklist final {
public:
// An entry in the blocklist. Represents a device that should not be
// accessible using the Web Serial API. Currently only USB devices can be
// matched by an entry but this could be expanded in the future to support a
// more expressive ruleset.
struct Entry {
constexpr Entry(uint16_t usb_vendor_id, uint16_t usb_product_id)
: usb_vendor_id(usb_vendor_id), usb_product_id(usb_product_id) {}
// Matched against the idVendor field of the USB Device Descriptor.
uint16_t usb_vendor_id;
// Matched against the idProduct field of the USB Device Descriptor.
uint16_t usb_product_id;
};
SerialBlocklist(const SerialBlocklist&) = delete;
SerialBlocklist& operator=(const SerialBlocklist&) = delete;
~SerialBlocklist();
// Returns a singleton instance of the blocklist.
static SerialBlocklist& Get();
// Returns if a device is excluded from access.
bool IsExcluded(const device::mojom::SerialPortInfo& port_info) const;
// Size of the blocklist.
size_t GetDynamicEntryCountForTesting() const {
return dynamic_entries_.size();
}
// Reload the blocklist for testing purposes.
void ResetToDefaultValuesForTesting();
private:
// Friend NoDestructor to permit access to private constructor.
friend class base::NoDestructor<SerialBlocklist>;
SerialBlocklist();
// Populates the blocklist with values set via a Finch experiment which allows
// the set of blocked devices to be updated without shipping new executable
// versions.
//
// See kWebSerialBlocklistAdditions for the format of this parameter.
void PopulateWithServerProvidedValues();
// Set of blocklist entries.
std::vector<Entry> dynamic_entries_;
};
#endif // CHROME_BROWSER_SERIAL_SERIAL_BLOCKLIST_H_
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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_blocklist.h"
#include "base/strings/string_piece.h"
#include "base/test/scoped_feature_list.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
class SerialBlocklistTest : public testing::Test {
public:
void SetDynamicBlocklist(base::StringPiece value) {
feature_list_.Reset();
std::map<std::string, std::string> parameters;
parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
feature_list_.InitWithFeaturesAndParameters(
{{kWebSerialBlocklist, parameters}}, {});
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
device::mojom::SerialPortInfoPtr CreateInfo(uint16_t usb_vendor_id,
uint16_t usb_product_id) {
auto info = device::mojom::SerialPortInfo::New();
info->has_vendor_id = true;
info->vendor_id = usb_vendor_id;
info->has_product_id = true;
info->product_id = usb_product_id;
return info;
}
private:
void TearDown() override {
// Because SerialBlocklist is a singleton it must be cleared after tests run
// to prevent leakage between tests.
feature_list_.Reset();
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SerialBlocklistTest, BasicExclusions) {
SetDynamicBlocklist("usb:18D1:58F0");
EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
// Devices with nearby vendor and product IDs are not blocked.
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F1)));
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58EF)));
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D0, 0x58F0)));
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D2, 0x58F0)));
}
TEST_F(SerialBlocklistTest, NonUsbDevice) {
auto info = device::mojom::SerialPortInfo::New();
info->has_vendor_id = false;
info->has_product_id = false;
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*info));
}
TEST_F(SerialBlocklistTest, StringsWithNoValidEntries) {
SetDynamicBlocklist("");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("~!@#$%^&*()-_=+[]{}/*-");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist(":");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("::");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist(",");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist(",,");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist(",::,");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("usb:2:3");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("usb:18D1:2");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("usb:0000:0x00");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("usb:0000: 0");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("usb:000g:0000");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("bluetooth:0000:0000");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
SetDynamicBlocklist("☯");
EXPECT_EQ(0u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
}
TEST_F(SerialBlocklistTest, StringsWithOneValidEntry) {
SetDynamicBlocklist("usb:18D1:58F0");
EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
SetDynamicBlocklist(" usb:18D1:58F0 ");
EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
SetDynamicBlocklist(", usb:18D1:58F0, ");
EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
SetDynamicBlocklist("usb:18D1:58F0, bluetooth:18D1:58F1");
EXPECT_EQ(1u, SerialBlocklist::Get().GetDynamicEntryCountForTesting());
EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F0)));
}
TEST_F(SerialBlocklistTest, StaticEntries) {
EXPECT_TRUE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F3)));
// Devices with nearby vendor and product IDs are not blocked.
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F4)));
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D1, 0x58F2)));
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D0, 0x58F3)));
EXPECT_FALSE(SerialBlocklist::Get().IsExcluded(*CreateInfo(0x18D2, 0x58F3)));
}
......@@ -13,6 +13,7 @@
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "chrome/browser/serial/serial_chooser_histograms.h"
#include "content/public/browser/device_service.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
......@@ -226,6 +227,10 @@ bool SerialChooserContext::HasPortPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const device::mojom::SerialPortInfo& port) {
if (SerialBlocklist::Get().IsExcluded(port)) {
return false;
}
if (!CanRequestObjectPermission(requesting_origin, embedding_origin)) {
return false;
}
......
......@@ -8,8 +8,10 @@
#include "base/run_loop.h"
#include "base/scoped_observer.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/serial/serial_chooser_histograms.h"
#include "chrome/test/base/testing_profile.h"
......@@ -79,6 +81,24 @@ class SerialChooserContextTest : public testing::Test {
SerialChooserContextTest(SerialChooserContextTest&) = delete;
SerialChooserContextTest& operator=(SerialChooserContextTest&) = delete;
void TearDown() override {
// Because SerialBlocklist is a singleton it must be cleared after tests run
// to prevent leakage between tests.
feature_list_.Reset();
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
void SetDynamicBlocklist(base::StringPiece value) {
feature_list_.Reset();
std::map<std::string, std::string> parameters;
parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
feature_list_.InitWithFeaturesAndParameters(
{{kWebSerialBlocklist, parameters}}, {});
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
device::FakeSerialPortManager& port_manager() { return port_manager_; }
TestingProfile* profile() { return &profile_; }
SerialChooserContext* context() { return context_; }
......@@ -89,6 +109,7 @@ class SerialChooserContextTest : public testing::Test {
private:
content::BrowserTaskEnvironment task_environment_;
base::test::ScopedFeatureList feature_list_;
device::FakeSerialPortManager port_manager_;
TestingProfile profile_;
SerialChooserContext* context_;
......@@ -439,3 +460,33 @@ TEST_F(SerialChooserContextTest, PolicyBlockedForUrls) {
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, all_origin_objects.size());
}
TEST_F(SerialChooserContextTest, Blocklist) {
const auto origin = url::Origin::Create(GURL("https://google.com"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->has_vendor_id = true;
port->vendor_id = 0x18D1;
port->has_product_id = true;
port->product_id = 0x58F0;
context()->GrantPortPermission(origin, origin, *port);
EXPECT_TRUE(context()->HasPortPermission(origin, origin, *port));
// Adding a USB device to the blocklist overrides any previously granted
// permissions.
SetDynamicBlocklist("usb:18D1:58F0");
EXPECT_FALSE(context()->HasPortPermission(origin, origin, *port));
// The lists of granted permissions will still include the entry because
// permission storage does not include the USB vendor and product IDs on all
// platforms and users should still be made aware of permissions they've
// granted even if they are being blocked from taking effect.
std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
objects = context()->GetGrantedObjects(origin, origin);
EXPECT_EQ(1u, objects.size());
std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, all_origin_objects.size());
}
......@@ -12,6 +12,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/unguessable_token.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/serial/serial_chooser_histograms.h"
#include "chrome/grit/generated_resources.h"
......@@ -114,7 +115,7 @@ void SerialChooserController::OpenHelpCenterUrl() const {
void SerialChooserController::OnPortAdded(
const device::mojom::SerialPortInfo& port) {
if (!FilterMatchesAny(port))
if (!DisplayDevice(port))
return;
ports_.push_back(port.Clone());
......@@ -148,7 +149,7 @@ void SerialChooserController::OnGetDevices(
});
for (auto& port : ports) {
if (FilterMatchesAny(*port))
if (DisplayDevice(*port))
ports_.push_back(std::move(port));
}
......@@ -156,8 +157,11 @@ void SerialChooserController::OnGetDevices(
view()->OnOptionsInitialized();
}
bool SerialChooserController::FilterMatchesAny(
bool SerialChooserController::DisplayDevice(
const device::mojom::SerialPortInfo& port) const {
if (SerialBlocklist::Get().IsExcluded(port))
return false;
if (filters_.empty())
return true;
......
......@@ -52,7 +52,7 @@ class SerialChooserController final
private:
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
bool FilterMatchesAny(const device::mojom::SerialPortInfo& port) const;
bool DisplayDevice(const device::mojom::SerialPortInfo& port) const;
void RunCallback(device::mojom::SerialPortInfoPtr port);
std::vector<blink::mojom::SerialPortFilterPtr> filters_;
......
......@@ -13,6 +13,7 @@
#include "base/test/mock_callback.h"
#include "build/build_config.h"
#include "chrome/browser/chooser_controller/mock_chooser_controller_view.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "chrome/browser/serial/serial_chooser_context.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/serial/serial_chooser_histograms.h"
......@@ -39,6 +40,15 @@ class SerialChooserControllerTest : public ChromeRenderViewHostTestHarness {
->SetPortManagerForTesting(std::move(port_manager));
}
void TearDown() override {
// Because SerialBlocklist is a singleton it must be cleared after tests run
// to prevent leakage between tests.
feature_list_.Reset();
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
ChromeRenderViewHostTestHarness::TearDown();
}
base::UnguessableToken AddPort(
const std::string& display_name,
const base::FilePath& path,
......@@ -61,9 +71,21 @@ class SerialChooserControllerTest : public ChromeRenderViewHostTestHarness {
return port_token;
}
void SetDynamicBlocklist(base::StringPiece value) {
feature_list_.Reset();
std::map<std::string, std::string> parameters;
parameters[kWebSerialBlocklistAdditions.name] = std::string(value);
feature_list_.InitWithFeaturesAndParameters(
{{kWebSerialBlocklist, parameters}}, {});
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
device::FakeSerialPortManager& port_manager() { return port_manager_; }
private:
base::test::ScopedFeatureList feature_list_;
device::FakeSerialPortManager port_manager_;
};
......@@ -268,3 +290,60 @@ TEST_F(SerialChooserControllerTest, PortFiltered) {
run_loop.Run();
}
}
TEST_F(SerialChooserControllerTest, Blocklist) {
base::HistogramTester histogram_tester;
// Create two ports from the same vendor with different product IDs.
base::UnguessableToken port_1 =
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
base::UnguessableToken port_2 =
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
0x1234, 0x0002);
// Add the second port to the blocklist.
SetDynamicBlocklist("usb:1234:0002");
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters), base::DoNothing());
MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
// Expect that only the first port is shown thanks to the filter.
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(base::ASCIIToUTF16("Test Port 1 (ttyS0)"),
controller->GetOption(0));
run_loop.Quit();
}));
run_loop.Run();
}
// Removing the second port should be a no-op since it is filtered out.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(port_2);
base::RunLoop().RunUntilIdle();
// Adding it back should be a no-op as well.
EXPECT_CALL(view, OnOptionAdded).Times(0);
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
0x1234, 0x0002);
base::RunLoop().RunUntilIdle();
// Removing the first port should trigger a change in the UI. This also acts
// as a synchronization point to make sure that the changes above were
// processed.
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionRemoved(0)).WillOnce(Invoke([&]() {
run_loop.Quit();
}));
port_manager().RemovePort(port_1);
run_loop.Run();
}
}
......@@ -4571,6 +4571,7 @@ test("unit_tests") {
"../browser/send_tab_to_self/send_tab_to_self_client_service_unittest.cc",
"../browser/send_tab_to_self/send_tab_to_self_desktop_util_unittest.cc",
"../browser/send_tab_to_self/send_tab_to_self_util_unittest.cc",
"../browser/serial/serial_blocklist_unittest.cc",
"../browser/serial/serial_chooser_context_unittest.cc",
"../browser/sessions/tab_restore_service_unittest.cc",
"../browser/signin/signin_promo_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment