blob: 4bac4e3fae961669b0c3284f406d5a787ac8017e [file] [log] [blame]
// Copyright 2014 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 "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 "device/core/device_client.h"
#include "device/usb/usb_device.h"
#include "device/usb/usb_device_handle.h"
#include "extensions/browser/api/device_permissions_manager.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/extension.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
using device::UsbDevice;
using device::UsbDeviceHandle;
using device::UsbService;
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgPointee;
int next_id;
class MockUsbService : public UsbService {
public:
MockUsbService() : mock_device_client(this) {}
MOCK_METHOD1(GetDeviceById, scoped_refptr<UsbDevice>(uint32));
MOCK_METHOD1(GetDevices, void(std::vector<scoped_refptr<UsbDevice>>*));
// Public wrapper for the protected NotifyDeviceRemove function.
void NotifyDeviceRemoved(scoped_refptr<UsbDevice> device) {
UsbService::NotifyDeviceRemoved(device);
}
private:
class MockDeviceClient : device::DeviceClient {
public:
explicit MockDeviceClient(UsbService* usb_service)
: usb_service_(usb_service) {}
UsbService* GetUsbService() override { return usb_service_; }
private:
UsbService* usb_service_;
};
MockDeviceClient mock_device_client;
};
class MockUsbDevice : public UsbDevice {
public:
explicit MockUsbDevice(const std::string& serial_number)
: UsbDevice(0, 0, next_id++) {
if (serial_number.empty()) {
EXPECT_CALL(*this, GetSerialNumber(_)).WillRepeatedly(Return(false));
} else {
EXPECT_CALL(*this, GetSerialNumber(_))
.WillRepeatedly(
DoAll(SetArgPointee<0>(base::ASCIIToUTF16(serial_number)),
Return(true)));
}
EXPECT_CALL(*this, GetProduct(_))
.WillRepeatedly(
DoAll(SetArgPointee<0>(base::ASCIIToUTF16("Test Product")),
Return(true)));
EXPECT_CALL(*this, GetManufacturer(_))
.WillRepeatedly(
DoAll(SetArgPointee<0>(base::ASCIIToUTF16("Test Manufacturer")),
Return(true)));
}
MOCK_METHOD2(RequestUsbAccess, void(int, const base::Callback<void(bool)>&));
MOCK_METHOD0(Open, scoped_refptr<UsbDeviceHandle>());
MOCK_METHOD1(Close, bool(scoped_refptr<UsbDeviceHandle>));
MOCK_METHOD0(GetConfiguration, const device::UsbConfigDescriptor*());
MOCK_METHOD1(GetManufacturer, bool(base::string16*));
MOCK_METHOD1(GetProduct, bool(base::string16*));
MOCK_METHOD1(GetSerialNumber, bool(base::string16*));
private:
virtual ~MockUsbDevice() {}
};
void AllowUsbDevice(DevicePermissionsManager* manager,
const Extension* extension,
scoped_refptr<UsbDevice> device) {
// If the device cannot provide any of these strings they will simply by
// empty.
base::string16 product;
device->GetProduct(&product);
base::string16 manufacturer;
device->GetManufacturer(&manufacturer);
base::string16 serial_number;
device->GetSerialNumber(&serial_number);
manager->AllowUsbDevice(
extension->id(), device, product, manufacturer, serial_number);
}
scoped_refptr<DevicePermissionEntry> FindEntry(
DevicePermissions* device_permissions,
scoped_refptr<UsbDevice> device) {
base::string16 serial_number;
device->GetSerialNumber(&serial_number);
return device_permissions->FindEntry(device, serial_number);
}
} // namespace
class DevicePermissionsManagerTest : public testing::Test {
protected:
void SetUp() override {
testing::Test::SetUp();
env_.GetExtensionPrefs(); // Force creation before adding extensions.
extension_ = env_.MakeExtension(*base::test::ParseJson(
"{"
" \"app\": {"
" \"background\": {"
" \"scripts\": [\"background.js\"]"
" }"
" },"
" \"permissions\": ["
" \"usb\""
" ]"
"}"));
device0_ = new MockUsbDevice("ABCDE");
device1_ = new MockUsbDevice("");
device2_ = new MockUsbDevice("12345");
device3_ = new MockUsbDevice("");
usb_service_ = new MockUsbService();
UsbService::SetInstanceForTest(usb_service_);
}
extensions::TestExtensionEnvironment env_;
const extensions::Extension* extension_;
MockUsbService* usb_service_;
scoped_refptr<MockUsbDevice> device0_;
scoped_refptr<MockUsbDevice> device1_;
scoped_refptr<MockUsbDevice> device2_;
scoped_refptr<MockUsbDevice> device3_;
};
TEST_F(DevicePermissionsManagerTest, AllowAndClearDevices) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
scoped_refptr<DevicePermissionEntry> device0_entry =
FindEntry(device_permissions.get(), device0_);
ASSERT_TRUE(device0_entry.get());
scoped_refptr<DevicePermissionEntry> device1_entry =
FindEntry(device_permissions.get(), device1_);
ASSERT_TRUE(device1_entry.get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
ASSERT_EQ(2U, device_permissions->entries().size());
ASSERT_EQ(base::ASCIIToUTF16(
"Test Product from Test Manufacturer (serial number ABCDE)"),
device0_entry->GetPermissionMessageString());
ASSERT_EQ(base::ASCIIToUTF16("Test Product from Test Manufacturer"),
device1_entry->GetPermissionMessageString());
manager->Clear(extension_->id());
device_permissions = manager->GetForExtension(extension_->id());
ASSERT_FALSE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
ASSERT_EQ(0U, device_permissions->entries().size());
// After clearing device it should be possible to grant permission again.
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
device_permissions = manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_TRUE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
}
TEST_F(DevicePermissionsManagerTest, SuspendExtension) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_TRUE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
manager->OnBackgroundHostClose(extension_->id());
device_permissions = manager->GetForExtension(extension_->id());
// Device 0 is still registered because its serial number has been stored in
// ExtensionPrefs, it is "persistent".
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
// Device 1 does not have uniquely identifying traits and so permission to
// open it has been dropped when the app's windows have closed and the
// background page has been suspended.
ASSERT_FALSE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
}
// TODO(reillyg): Until crbug.com/427985 is resolved device removal
// notifications are delivered asynchronously and so this test must be disabled.
TEST_F(DevicePermissionsManagerTest, DISABLED_DisconnectDevice) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_TRUE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
usb_service_->NotifyDeviceRemoved(device0_);
usb_service_->NotifyDeviceRemoved(device1_);
device_permissions = manager->GetForExtension(extension_->id());
// Device 0 will be accessible when it is reconnected because it can be
// recognized by its serial number.
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
// 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.
ASSERT_FALSE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
}
TEST_F(DevicePermissionsManagerTest, RevokeAndRegrantAccess) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
AllowUsbDevice(manager, extension_, device1_);
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
scoped_refptr<DevicePermissionEntry> device0_entry =
FindEntry(device_permissions.get(), device0_);
ASSERT_TRUE(device0_entry.get());
scoped_refptr<DevicePermissionEntry> device1_entry =
FindEntry(device_permissions.get(), device1_);
ASSERT_TRUE(device1_entry.get());
manager->RemoveEntry(extension_->id(), device0_entry);
device_permissions = manager->GetForExtension(extension_->id());
ASSERT_FALSE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_TRUE(FindEntry(device_permissions.get(), device1_).get());
AllowUsbDevice(manager, extension_, device0_);
device_permissions = manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_TRUE(FindEntry(device_permissions.get(), device1_).get());
manager->RemoveEntry(extension_->id(), device1_entry);
device_permissions = manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device1_).get());
AllowUsbDevice(manager, extension_, device1_);
device_permissions = manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_TRUE(FindEntry(device_permissions.get(), device1_).get());
}
TEST_F(DevicePermissionsManagerTest, UpdateLastUsed) {
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_.profile());
AllowUsbDevice(manager, extension_, device0_);
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
scoped_refptr<DevicePermissionEntry> device0_entry =
FindEntry(device_permissions.get(), device0_);
ASSERT_TRUE(device0_entry->last_used().is_null());
manager->UpdateLastUsed(extension_->id(), device0_entry);
device_permissions = manager->GetForExtension(extension_->id());
device0_entry = FindEntry(device_permissions.get(), device0_);
ASSERT_FALSE(device0_entry->last_used().is_null());
}
TEST_F(DevicePermissionsManagerTest, LoadPrefs) {
scoped_ptr<base::Value> prefs_value = base::test::ParseJson(
"["
" {"
" \"product_id\": 0,"
" \"serial_number\": \"ABCDE\","
" \"type\": \"usb\","
" \"vendor_id\": 0"
" }"
"]");
env_.GetExtensionPrefs()->UpdateExtensionPref(extension_->id(), "devices",
prefs_value.release());
DevicePermissionsManager* manager =
DevicePermissionsManager::Get(env_.profile());
scoped_ptr<DevicePermissions> device_permissions =
manager->GetForExtension(extension_->id());
ASSERT_TRUE(FindEntry(device_permissions.get(), device0_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device1_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device2_).get());
ASSERT_FALSE(FindEntry(device_permissions.get(), device3_).get());
}
} // namespace extensions