blob: 4ea5c30e1bea365593dfa04abbc9d87e9a223b28 [file] [log] [blame]
// Copyright 2019 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_chooser_context.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.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/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/test/object_permission_context_base_mock_permission_observer.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "services/device/public/cpp/test/fake_serial_port_manager.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/scoped_user_manager.h"
#endif
namespace {
using ::base::test::ParseJson;
using ::testing::NiceMock;
constexpr char kTestUserEmail[] = "[email protected]";
class MockPortObserver : public SerialChooserContext::PortObserver {
public:
MockPortObserver() = default;
MockPortObserver(MockPortObserver&) = delete;
MockPortObserver& operator=(MockPortObserver&) = delete;
~MockPortObserver() override = default;
MOCK_METHOD1(OnPortAdded, void(const device::mojom::SerialPortInfo&));
MOCK_METHOD1(OnPortRemoved, void(const device::mojom::SerialPortInfo&));
MOCK_METHOD0(OnPortManagerConnectionError, void());
MOCK_METHOD1(OnPermissionRevoked, void(const url::Origin&));
};
device::mojom::SerialPortInfoPtr CreatePersistentPort(
absl::optional<std::string> name,
const std::string& persistent_id) {
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->display_name = std::move(name);
#if BUILDFLAG(IS_WIN)
port->device_instance_id = persistent_id;
#else
port->has_vendor_id = true;
port->vendor_id = 0;
port->has_product_id = true;
port->product_id = 0;
port->serial_number = persistent_id;
#if BUILDFLAG(IS_MAC)
port->usb_driver_name = "AppleUSBCDC";
#endif
#endif // BUILDFLAG(IS_WIN)
return port;
}
class SerialChooserContextTestBase {
public:
SerialChooserContextTestBase() = default;
~SerialChooserContextTestBase() = default;
// Disallow copy and assignment.
SerialChooserContextTestBase(SerialChooserContextTestBase&) = delete;
SerialChooserContextTestBase& operator=(SerialChooserContextTestBase&) =
delete;
void DoSetUp(bool is_affiliated) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>();
auto* fake_user_manager_ptr = fake_user_manager.get();
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(fake_user_manager));
constexpr char kTestUserGaiaId[] = "1111111111";
auto account_id =
AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
fake_user_manager_ptr->AddUserWithAffiliation(account_id, is_affiliated);
fake_user_manager_ptr->LoginUser(account_id);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
testing_profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(testing_profile_manager_->SetUp());
profile_ = testing_profile_manager_->CreateTestingProfile(kTestUserEmail);
ASSERT_TRUE(profile_);
mojo::PendingRemote<device::mojom::SerialPortManager> port_manager;
port_manager_.AddReceiver(port_manager.InitWithNewPipeAndPassReceiver());
context_ = SerialChooserContextFactory::GetForProfile(profile_);
context_->SetPortManagerForTesting(std::move(port_manager));
scoped_permission_observation_.Observe(context_.get());
scoped_port_observation_.Observe(context_.get());
// Ensure |context_| is ready to receive SerialPortManagerClient messages.
context_->FlushPortManagerConnectionForTesting();
}
void DoTearDown() {
// 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_; }
TestingPrefServiceSimple* local_state() {
return testing_profile_manager_->local_state()->Get();
}
SerialChooserContext* context() { return context_; }
permissions::MockPermissionObserver& permission_observer() {
return permission_observer_;
}
NiceMock<MockPortObserver>& port_observer() { return port_observer_; }
private:
content::BrowserTaskEnvironment task_environment_;
base::test::ScopedFeatureList feature_list_;
device::FakeSerialPortManager port_manager_;
std::unique_ptr<TestingProfileManager> testing_profile_manager_;
raw_ptr<TestingProfile> profile_ = nullptr;
#if BUILDFLAG(IS_CHROMEOS_ASH)
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
#endif
raw_ptr<SerialChooserContext> context_;
NiceMock<permissions::MockPermissionObserver> permission_observer_;
base::ScopedObservation<
permissions::ObjectPermissionContextBase,
permissions::ObjectPermissionContextBase::PermissionObserver>
scoped_permission_observation_{&permission_observer_};
NiceMock<MockPortObserver> port_observer_;
base::ScopedObservation<SerialChooserContext,
SerialChooserContext::PortObserver>
scoped_port_observation_{&port_observer_};
};
class SerialChooserContextTest : public SerialChooserContextTestBase,
public testing::Test {
public:
void SetUp() override { DoSetUp(/*is_affiliated=*/true); }
void TearDown() override { DoTearDown(); }
};
class SerialChooserContextAffiliatedTest : public SerialChooserContextTestBase,
public testing::TestWithParam<bool> {
public:
SerialChooserContextAffiliatedTest() : is_affiliated_(GetParam()) {}
void SetUp() override { DoSetUp(is_affiliated_); }
void TearDown() override { DoTearDown(); }
bool is_affiliated() const { return is_affiliated_; }
private:
bool is_affiliated_;
};
} // namespace
TEST_F(SerialChooserContextTest, GrantAndRevokeEphemeralPermission) {
base::HistogramTester histogram_tester;
const auto origin = url::Origin::Create(GURL("https://google.com"));
auto port_1 = device::mojom::SerialPortInfo::New();
port_1->token = base::UnguessableToken::Create();
auto port_2 = CreatePersistentPort("Persistent Port", "ABC123");
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
context()->GrantPortPermission(origin, *port_1);
EXPECT_TRUE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
std::vector<std::unique_ptr<SerialChooserContext::Object>> origin_objects =
context()->GetGrantedObjects(origin);
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin.GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin));
context()->RevokeObjectPermission(origin, objects[0]->value);
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, origin_objects.size());
objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
histogram_tester.ExpectUniqueSample("Permissions.Serial.Revoked",
SerialPermissionRevoked::kEphemeralByUser,
1);
}
TEST_F(SerialChooserContextTest, RevokeEphemeralPermissionByWebsite) {
base::HistogramTester histogram_tester;
const auto origin = url::Origin::Create(GURL("https://google.com"));
auto port_1 = device::mojom::SerialPortInfo::New();
port_1->token = base::UnguessableToken::Create();
auto port_2 = CreatePersistentPort("Persistent Port", "ABC123");
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
context()->GrantPortPermission(origin, *port_1);
EXPECT_TRUE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
std::vector<std::unique_ptr<SerialChooserContext::Object>> origin_objects =
context()->GetGrantedObjects(origin);
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin.GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin));
context()->RevokePortPermissionWebInitiated(origin, port_1->token);
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, origin_objects.size());
objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
histogram_tester.ExpectUniqueSample(
"Permissions.Serial.Revoked",
SerialPermissionRevoked::kEphemeralByWebsite, 1);
}
TEST_F(SerialChooserContextTest, GrantAndRevokePersistentPermission) {
base::HistogramTester histogram_tester;
const auto origin = url::Origin::Create(GURL("https://google.com"));
device::mojom::SerialPortInfoPtr port_1 =
CreatePersistentPort("Persistent Port", "ABC123");
auto port_2 = device::mojom::SerialPortInfo::New();
port_2->token = base::UnguessableToken::Create();
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
context()->GrantPortPermission(origin, *port_1);
EXPECT_TRUE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
std::vector<std::unique_ptr<SerialChooserContext::Object>> origin_objects =
context()->GetGrantedObjects(origin);
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin.GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin));
context()->RevokeObjectPermission(origin, objects[0]->value);
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, origin_objects.size());
objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
histogram_tester.ExpectUniqueSample(
"Permissions.Serial.Revoked", SerialPermissionRevoked::kPersistentByUser,
1);
}
TEST_F(SerialChooserContextTest, RevokePersistentPermissionByWebsite) {
base::HistogramTester histogram_tester;
const auto origin = url::Origin::Create(GURL("https://google.com"));
device::mojom::SerialPortInfoPtr port_1 =
CreatePersistentPort("Persistent Port", "ABC123");
auto port_2 = device::mojom::SerialPortInfo::New();
port_2->token = base::UnguessableToken::Create();
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
context()->GrantPortPermission(origin, *port_1);
EXPECT_TRUE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
std::vector<std::unique_ptr<SerialChooserContext::Object>> origin_objects =
context()->GetGrantedObjects(origin);
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin.GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA));
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin));
context()->RevokePortPermissionWebInitiated(origin, port_1->token);
EXPECT_FALSE(context()->HasPortPermission(origin, *port_1));
EXPECT_FALSE(context()->HasPortPermission(origin, *port_2));
origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, origin_objects.size());
objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
histogram_tester.ExpectUniqueSample(
"Permissions.Serial.Revoked",
SerialPermissionRevoked::kPersistentByWebsite, 1);
}
TEST_F(SerialChooserContextTest, EphemeralPermissionRevokedOnDisconnect) {
base::HistogramTester histogram_tester;
const auto origin = url::Origin::Create(GURL("https://google.com"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port_manager().AddPort(port.Clone());
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA))
.Times(2);
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin));
context()->GrantPortPermission(origin, *port);
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
port_manager().RemovePort(port->token);
{
base::RunLoop run_loop;
EXPECT_CALL(port_observer(), OnPortRemoved(testing::_))
.WillOnce(
testing::Invoke([&](const device::mojom::SerialPortInfo& info) {
EXPECT_EQ(port->token, info.token);
EXPECT_TRUE(context()->HasPortPermission(origin, info));
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_FALSE(context()->HasPortPermission(origin, *port));
auto origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, origin_objects.size());
auto objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
histogram_tester.ExpectUniqueSample(
"Permissions.Serial.Revoked",
SerialPermissionRevoked::kEphemeralByDisconnect, 1);
}
TEST_F(SerialChooserContextTest, PersistenceRequiresDisplayName) {
const auto origin = url::Origin::Create(GURL("https://google.com"));
device::mojom::SerialPortInfoPtr port =
CreatePersistentPort(/*name=*/absl::nullopt, "ABC123");
port_manager().AddPort(port.Clone());
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA))
.Times(2);
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin));
context()->GrantPortPermission(origin, *port);
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
// Without a display name a persistent permission cannot be recorded and so
// removing the device will revoke permission.
port_manager().RemovePort(port->token);
{
base::RunLoop run_loop;
EXPECT_CALL(port_observer(), OnPortRemoved(testing::_))
.WillOnce(
testing::Invoke([&](const device::mojom::SerialPortInfo& info) {
EXPECT_EQ(port->token, info.token);
EXPECT_TRUE(context()->HasPortPermission(origin, info));
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_FALSE(context()->HasPortPermission(origin, *port));
auto origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, origin_objects.size());
auto objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
}
TEST_F(SerialChooserContextTest, PersistentPermissionNotRevokedOnDisconnect) {
const auto origin = url::Origin::Create(GURL("https://google.com"));
const char persistent_id[] = "ABC123";
device::mojom::SerialPortInfoPtr port =
CreatePersistentPort("Persistent Port", persistent_id);
port_manager().AddPort(port.Clone());
context()->GrantPortPermission(origin, *port);
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
absl::make_optional(ContentSettingsType::SERIAL_GUARD),
ContentSettingsType::SERIAL_CHOOSER_DATA))
.Times(0);
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin)).Times(0);
port_manager().RemovePort(port->token);
{
base::RunLoop run_loop;
EXPECT_CALL(port_observer(), OnPortRemoved(testing::_))
.WillOnce(
testing::Invoke([&](const device::mojom::SerialPortInfo& info) {
EXPECT_EQ(port->token, info.token);
EXPECT_TRUE(context()->HasPortPermission(origin, info));
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
auto origin_objects = context()->GetGrantedObjects(origin);
EXPECT_EQ(1u, origin_objects.size());
auto objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, objects.size());
// Simulate reconnection of the port. It gets a new token but the same
// persistent ID. This SerialPortInfo should still match the old permission.
port = CreatePersistentPort("Persistent Port", persistent_id);
port_manager().AddPort(port.Clone());
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
}
TEST_F(SerialChooserContextTest, GuardPermission) {
const auto origin = url::Origin::Create(GURL("https://google.com"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
context()->GrantPortPermission(origin, *port);
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
map->SetContentSettingDefaultScope(origin.GetURL(), origin.GetURL(),
ContentSettingsType::SERIAL_GUARD,
CONTENT_SETTING_BLOCK);
EXPECT_FALSE(context()->HasPortPermission(origin, *port));
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>>
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, all_origin_objects.size());
}
TEST_F(SerialChooserContextTest, PolicyGuardPermission) {
const auto origin = url::Origin::Create(GURL("https://google.com"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
context()->GrantPortPermission(origin, *port);
auto* profile_prefs = profile()->GetTestingPrefService();
profile_prefs->SetManagedPref(
prefs::kManagedDefaultSerialGuardSetting,
std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
EXPECT_FALSE(context()->CanRequestObjectPermission(origin));
EXPECT_FALSE(context()->HasPortPermission(origin, *port));
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetGrantedObjects(origin);
EXPECT_EQ(0u, objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>>
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, all_origin_objects.size());
}
TEST_F(SerialChooserContextTest, PolicyAskForUrls) {
const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
context()->GrantPortPermission(kFooOrigin, *port);
context()->GrantPortPermission(kBarOrigin, *port);
// Set the default to "ask" so that the policy being tested overrides it.
auto* profile_prefs = profile()->GetTestingPrefService();
profile_prefs->SetManagedPref(
prefs::kManagedDefaultSerialGuardSetting,
std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
profile_prefs->SetManagedPref(prefs::kManagedSerialAskForUrls,
ParseJson(R"([ "https://foo.origin" ])"));
EXPECT_TRUE(context()->CanRequestObjectPermission(kFooOrigin));
EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *port));
EXPECT_FALSE(context()->CanRequestObjectPermission(kBarOrigin));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *port));
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetGrantedObjects(kFooOrigin);
EXPECT_EQ(1u, objects.size());
objects = context()->GetGrantedObjects(kBarOrigin);
EXPECT_EQ(0u, objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>>
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, all_origin_objects.size());
}
TEST_F(SerialChooserContextTest, PolicyBlockedForUrls) {
const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
context()->GrantPortPermission(kFooOrigin, *port);
context()->GrantPortPermission(kBarOrigin, *port);
auto* profile_prefs = profile()->GetTestingPrefService();
profile_prefs->SetManagedPref(prefs::kManagedSerialBlockedForUrls,
ParseJson(R"([ "https://foo.origin" ])"));
EXPECT_FALSE(context()->CanRequestObjectPermission(kFooOrigin));
EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *port));
EXPECT_TRUE(context()->CanRequestObjectPermission(kBarOrigin));
EXPECT_TRUE(context()->HasPortPermission(kBarOrigin, *port));
std::vector<std::unique_ptr<SerialChooserContext::Object>> objects =
context()->GetGrantedObjects(kFooOrigin);
EXPECT_EQ(0u, objects.size());
objects = context()->GetGrantedObjects(kBarOrigin);
EXPECT_EQ(1u, objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>>
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, all_origin_objects.size());
}
TEST_P(SerialChooserContextAffiliatedTest, PolicyAllowForUrls) {
const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
local_state()->SetManagedPref(prefs::kManagedSerialAllowAllPortsForUrls,
ParseJson(R"([ "https://foo.origin" ])"));
local_state()->SetManagedPref(prefs::kManagedSerialAllowUsbDevicesForUrls,
ParseJson(R"([
{
"devices": [{ "vendor_id": 6353, "product_id": 19985 }],
"urls": [ "https://bar.origin" ]
}
])"));
auto platform_port = device::mojom::SerialPortInfo::New();
platform_port->token = base::UnguessableToken::Create();
auto usb_port1 = device::mojom::SerialPortInfo::New();
usb_port1->token = base::UnguessableToken::Create();
usb_port1->has_vendor_id = true;
usb_port1->vendor_id = 0x18D1;
usb_port1->has_product_id = true;
usb_port1->product_id = 0x4E11;
auto usb_port2 = device::mojom::SerialPortInfo::New();
usb_port2->token = base::UnguessableToken::Create();
usb_port2->has_vendor_id = true;
usb_port2->vendor_id = 0x18D1;
usb_port2->has_product_id = true;
usb_port2->product_id = 0x4E12;
EXPECT_TRUE(context()->CanRequestObjectPermission(kFooOrigin));
EXPECT_TRUE(context()->CanRequestObjectPermission(kBarOrigin));
if (is_affiliated()) {
EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *platform_port));
EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *usb_port1));
EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *usb_port2));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *platform_port));
EXPECT_TRUE(context()->HasPortPermission(kBarOrigin, *usb_port1));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *usb_port2));
auto foo_objects = context()->GetGrantedObjects(kFooOrigin);
ASSERT_EQ(1u, foo_objects.size());
const auto& foo_object = foo_objects.front();
EXPECT_EQ(kFooOrigin.GetURL(), foo_object->origin);
EXPECT_EQ(u"Any serial port",
context()->GetObjectDisplayName(foo_object->value));
EXPECT_EQ(content_settings::SETTING_SOURCE_POLICY, foo_object->source);
EXPECT_FALSE(foo_object->incognito);
auto bar_objects = context()->GetGrantedObjects(kBarOrigin);
ASSERT_EQ(1u, bar_objects.size());
const auto& bar_object = bar_objects.front();
EXPECT_EQ(kBarOrigin.GetURL(), bar_object->origin);
EXPECT_EQ(u"Nexus One", context()->GetObjectDisplayName(bar_object->value));
EXPECT_EQ(content_settings::SETTING_SOURCE_POLICY, bar_object->source);
EXPECT_FALSE(bar_object->incognito);
auto all_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(2u, all_objects.size());
bool found_foo_object = false, found_bar_object = false;
for (const auto& object : all_objects) {
if (object->origin == kFooOrigin.GetURL()) {
EXPECT_FALSE(found_foo_object);
found_foo_object = true;
EXPECT_EQ(u"Any serial port",
context()->GetObjectDisplayName(object->value));
} else if (object->origin == kBarOrigin.GetURL()) {
EXPECT_FALSE(found_bar_object);
found_bar_object = true;
EXPECT_EQ(u"Nexus One", context()->GetObjectDisplayName(object->value));
}
EXPECT_EQ(content_settings::SETTING_SOURCE_POLICY, object->source);
EXPECT_FALSE(object->incognito);
}
EXPECT_TRUE(found_foo_object);
EXPECT_TRUE(found_bar_object);
} else {
// Policy-defined port permissions are not set for unaffiliated users.
EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *platform_port));
EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *usb_port1));
EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *usb_port2));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *platform_port));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *usb_port1));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *usb_port2));
auto foo_objects = context()->GetGrantedObjects(kFooOrigin);
EXPECT_EQ(0u, foo_objects.size());
auto bar_objects = context()->GetGrantedObjects(kBarOrigin);
EXPECT_EQ(0u, bar_objects.size());
auto all_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(0u, all_objects.size());
}
}
TEST_P(SerialChooserContextAffiliatedTest,
PolicyAllowForUrlsDescriptionStrings) {
const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
local_state()->SetManagedPref(prefs::kManagedSerialAllowUsbDevicesForUrls,
ParseJson(R"([
{
"devices": [{ "vendor_id": 6353 }],
"urls": [ "https://google.com" ]
},
{
"devices": [{ "vendor_id": 6354 }],
"urls": [ "https://unknown-vendor.com" ]
},
{
"devices": [{ "vendor_id": 6353, "product_id": 5678 }],
"urls": [ "https://unknown-product.google.com" ]
},
{
"devices": [{ "vendor_id": 6354, "product_id": 5678 }],
"urls": [ "https://unknown-product.unknown-vendor.com" ]
}
])"));
if (is_affiliated()) {
auto google_objects = context()->GetGrantedObjects(
url::Origin::Create(GURL("https://google.com")));
ASSERT_EQ(1u, google_objects.size());
EXPECT_EQ(u"USB devices from Google Inc.",
context()->GetObjectDisplayName(google_objects[0]->value));
auto unknown_vendor_objects = context()->GetGrantedObjects(
url::Origin::Create(GURL("https://unknown-vendor.com")));
ASSERT_EQ(1u, unknown_vendor_objects.size());
EXPECT_EQ(
u"USB devices from vendor 18D2",
context()->GetObjectDisplayName(unknown_vendor_objects[0]->value));
auto unknown_product_objects = context()->GetGrantedObjects(
url::Origin::Create(GURL("https://unknown-product.google.com")));
ASSERT_EQ(1u, unknown_product_objects.size());
EXPECT_EQ(
u"USB device from Google Inc. (product 162E)",
context()->GetObjectDisplayName(unknown_product_objects[0]->value));
auto unknown_product_and_vendor_objects =
context()->GetGrantedObjects(url::Origin::Create(
GURL("https://unknown-product.unknown-vendor.com")));
ASSERT_EQ(1u, unknown_product_and_vendor_objects.size());
EXPECT_EQ(u"USB device (18D2:162E)",
context()->GetObjectDisplayName(
unknown_product_and_vendor_objects[0]->value));
} else {
// Policy-defined port permissions are not set for unaffiliated users.
auto google_objects = context()->GetGrantedObjects(
url::Origin::Create(GURL("https://google.com")));
EXPECT_EQ(0u, google_objects.size());
auto unknown_vendor_objects = context()->GetGrantedObjects(
url::Origin::Create(GURL("https://unknown-vendor.com")));
EXPECT_EQ(0u, unknown_vendor_objects.size());
auto unknown_product_objects = context()->GetGrantedObjects(
url::Origin::Create(GURL("https://unknown-product.google.com")));
EXPECT_EQ(0u, unknown_product_objects.size());
auto unknown_product_and_vendor_objects =
context()->GetGrantedObjects(url::Origin::Create(
GURL("https://unknown-product.unknown-vendor.com")));
EXPECT_EQ(0u, unknown_product_and_vendor_objects.size());
}
}
TEST_P(SerialChooserContextAffiliatedTest, PolicyAllowOverridesGuard) {
const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
auto* profile_prefs = profile()->GetTestingPrefService();
profile_prefs->SetManagedPref(
prefs::kManagedDefaultSerialGuardSetting,
std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
local_state()->SetManagedPref(prefs::kManagedSerialAllowAllPortsForUrls,
ParseJson(R"([ "https://foo.origin" ])"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
EXPECT_FALSE(context()->CanRequestObjectPermission(kFooOrigin));
if (is_affiliated()) {
EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *port));
} else {
// Policy-defined port permissions are not set for unaffiliated users.
EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *port));
}
EXPECT_FALSE(context()->CanRequestObjectPermission(kBarOrigin));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *port));
}
TEST_P(SerialChooserContextAffiliatedTest, PolicyAllowOverridesBlocked) {
const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
auto* profile_prefs = profile()->GetTestingPrefService();
profile_prefs->SetManagedPref(
prefs::kManagedSerialBlockedForUrls,
ParseJson(R"([ "https://foo.origin", "https://bar.origin" ])"));
local_state()->SetManagedPref(prefs::kManagedSerialAllowAllPortsForUrls,
ParseJson(R"([ "https://foo.origin" ])"));
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
EXPECT_FALSE(context()->CanRequestObjectPermission(kFooOrigin));
if (is_affiliated()) {
EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *port));
} else {
// Policy-defined port permissions are not set for unaffiliated users.
EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *port));
}
EXPECT_FALSE(context()->CanRequestObjectPermission(kBarOrigin));
EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *port));
}
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, *port);
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
// Adding a USB device to the blocklist overrides any previously granted
// permissions.
SetDynamicBlocklist("usb:18D1:58F0");
EXPECT_FALSE(context()->HasPortPermission(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<SerialChooserContext::Object>> objects =
context()->GetGrantedObjects(origin);
EXPECT_EQ(1u, objects.size());
std::vector<std::unique_ptr<SerialChooserContext::Object>>
all_origin_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, all_origin_objects.size());
}
TEST_P(SerialChooserContextAffiliatedTest, BlocklistOverridesPolicy) {
const auto origin = url::Origin::Create(GURL("https://google.com"));
local_state()->SetManagedPref(prefs::kManagedSerialAllowUsbDevicesForUrls,
ParseJson(R"([
{
"devices": [{ "vendor_id": 6353, "product_id": 22768 }],
"urls": [ "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;
if (is_affiliated()) {
EXPECT_TRUE(context()->HasPortPermission(origin, *port));
} else {
// Policy-defined port permissions are not set for unaffiliated users.
EXPECT_FALSE(context()->HasPortPermission(origin, *port));
}
// Adding a USB device to the blocklist overrides permissions granted by
// policy.
SetDynamicBlocklist("usb:18D1:58F0");
EXPECT_FALSE(context()->HasPortPermission(origin, *port));
}
// Boolean parameter means if user is affiliated on the device. Affiliated
// users belong to the domain that owns the device and is only meaningful
// on Chrome OS.
//
// The SerialAllowAllPortsForUrls and SerialAllowUsbDevicesForUrls policies
// only take effect for sffiliated users.
INSTANTIATE_TEST_SUITE_P(
SerialChooserContextAffiliatedTestInstance,
SerialChooserContextAffiliatedTest,
#if BUILDFLAG(IS_CHROMEOS_ASH)
testing::Values(true, false),
#else
testing::Values(true),
#endif
[](const testing::TestParamInfo<
SerialChooserContextAffiliatedTest::ParamType>& info) {
return info.param ? "affiliated" : "unaffiliated";
});