chromium/chrome/browser/ash/app_list/arc/arc_usb_host_permission_manager.cc

// Copyright 2018 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/ash/app_list/arc/arc_usb_host_permission_manager.h"

#include <utility>
#include <vector>

#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/usb/usb_host_bridge.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/app_list/arc/arc_app_dialog.h"
#include "chrome/browser/ash/app_list/arc/arc_usb_host_permission_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "extensions/browser/api/device_permissions_manager.h"

namespace arc {

namespace {

constexpr char kUsbAccessPermission[] = "usb_access_permission";
constexpr char kUsbDeviceName[] = "usb_device_name";
constexpr char kUsbProductId[] = "usb_product_id";
constexpr char kUsbScanDeviceListPermission[] =
    "usb_scan_devicelist_permission";
constexpr char kUsbSerialNumber[] = "usb_serial_number";
constexpr char kUsbVendorId[] = "usb_vendor_id";

std::string GetAppIdFromPackageName(const std::string& package_name,
                                    const ArcAppListPrefs* arc_app_list_prefs) {
  DCHECK(arc_app_list_prefs);

  // For app icon and app name in UI dialog, find a matching launchable activity
  // for the requesting package. If there are multiple launchable activities
  // from the package, use the app icon from first found matching launchable
  // activity in the permission dialog.
  std::unordered_set<std::string> app_ids =
      arc_app_list_prefs->GetAppsForPackage(package_name);
  return app_ids.empty() ? std::string() : *app_ids.begin();
}

bool GetUint16FromDict(const base::Value::Dict& dict,
                       const std::string& key,
                       uint16_t* result) {
  std::optional<int> value = dict.FindInt(key);
  if (!value.has_value()) {
    return false;
  }
  int tmp = *value;
  if (tmp < 0 || tmp > UINT16_MAX)
    return false;
  *result = static_cast<uint16_t>(tmp);
  return true;
}

}  // namespace

// UsbPermissionRequest
ArcUsbHostPermissionManager::UsbPermissionRequest::UsbPermissionRequest(
    const std::string& package_name,
    bool is_scan_request,
    std::optional<UsbDeviceEntry> usb_device_entry,
    std::optional<ArcUsbHostUiDelegate::RequestPermissionCallback> callback)
    : package_name_(package_name),
      is_scan_request_(is_scan_request),
      usb_device_entry_(std::move(usb_device_entry)),
      callback_(std::move(callback)) {}

ArcUsbHostPermissionManager::UsbPermissionRequest::UsbPermissionRequest(
    ArcUsbHostPermissionManager::UsbPermissionRequest&& other) = default;

ArcUsbHostPermissionManager::UsbPermissionRequest&
ArcUsbHostPermissionManager::UsbPermissionRequest::operator=(
    ArcUsbHostPermissionManager::UsbPermissionRequest&& other) = default;

ArcUsbHostPermissionManager::UsbPermissionRequest::~UsbPermissionRequest() =
    default;

void ArcUsbHostPermissionManager::UsbPermissionRequest::Resolve(bool allowed) {
  if (!callback_)
    return;
  std::move(*callback_).Run(allowed);
}

// UsbDeviceEntry
ArcUsbHostPermissionManager::UsbDeviceEntry::UsbDeviceEntry(
    const std::string& guid,
    const std::u16string& device_name,
    const std::u16string& serial_number,
    uint16_t vendor_id,
    uint16_t product_id)
    : guid(guid),
      device_name(device_name),
      serial_number(serial_number),
      vendor_id(vendor_id),
      product_id(product_id) {}

ArcUsbHostPermissionManager::UsbDeviceEntry::UsbDeviceEntry(
    const ArcUsbHostPermissionManager::UsbDeviceEntry& other) = default;

ArcUsbHostPermissionManager::UsbDeviceEntry&
ArcUsbHostPermissionManager::UsbDeviceEntry::operator=(
    const ArcUsbHostPermissionManager::UsbDeviceEntry& other) = default;

bool ArcUsbHostPermissionManager::UsbDeviceEntry::Matches(
    const UsbDeviceEntry& other) const {
  if (IsPersistent() && other.IsPersistent()) {
    return serial_number == other.serial_number &&
           vendor_id == other.vendor_id && product_id == other.product_id;
  } else {
    return guid == other.guid;
  }
}

// static
ArcUsbHostPermissionManager* ArcUsbHostPermissionManager::GetForBrowserContext(
    content::BrowserContext* context) {
  return ArcUsbHostPermissionManagerFactory::GetForBrowserContext(context);
}

// static
std::unique_ptr<ArcUsbHostPermissionManager>
ArcUsbHostPermissionManager::Create(content::BrowserContext* context) {
  ArcAppListPrefs* arc_app_list_prefs = ArcAppListPrefs::Get(context);
  // TODO(lgcheng): Change this to DCHECK(arc_app_list_prefs) after clear the
  // browsertest workflow.
  if (!arc_app_list_prefs)
    return nullptr;

  ArcUsbHostBridge* arc_usb_host_bridge =
      ArcUsbHostBridge::GetForBrowserContext(context);
  if (!arc_usb_host_bridge)
    return nullptr;

  return std::make_unique<ArcUsbHostPermissionManager>(
      static_cast<Profile*>(context), arc_app_list_prefs, arc_usb_host_bridge);
}

ArcUsbHostPermissionManager::ArcUsbHostPermissionManager(
    Profile* profile,
    ArcAppListPrefs* arc_app_list_prefs,
    ArcUsbHostBridge* arc_usb_host_bridge)
    : profile_(profile), arc_app_list_prefs_(arc_app_list_prefs) {
  RestorePermissionFromChromePrefs();
  arc_app_list_prefs_->AddObserver(this);
  arc_usb_host_bridge->SetUiDelegate(this);
}

ArcUsbHostPermissionManager::~ArcUsbHostPermissionManager() {
  arc_app_list_prefs_->RemoveObserver(this);
}

void ArcUsbHostPermissionManager::RestorePermissionFromChromePrefs() {
  const std::vector<std::string> packages =
      arc_app_list_prefs_->GetPackagesFromPrefs();
  for (auto& package : packages) {
    // Restore scan device list permission.
    const base::Value* scan_permission_value =
        arc_app_list_prefs_->GetPackagePrefs(package,
                                             kUsbScanDeviceListPermission);
    if (scan_permission_value && scan_permission_value->is_bool() &&
        scan_permission_value->GetBool()) {
      usb_scan_devicelist_permission_packages_.emplace(package);
    }

    // Restore device access permission.
    const base::Value* access_permission_list_value =
        arc_app_list_prefs_->GetPackagePrefs(package, kUsbAccessPermission);
    if (!access_permission_list_value) {
      continue;
    }

    if (!access_permission_list_value->is_list()) {
      LOG(ERROR) << "Wrong value type found for device access permission list.";
      continue;
    }

    for (const auto& access_permission_value :
         access_permission_list_value->GetList()) {
      const base::Value::Dict& access_permission =
          access_permission_value.GetDict();
      const std::string* serial_number =
          access_permission.FindString(kUsbSerialNumber);
      if (!serial_number) {
        LOG(WARNING) << "Invalid device access permission: No serial number.";
        continue;
      }

      const std::string* device_name =
          access_permission.FindString(kUsbDeviceName);
      if (!device_name) {
        LOG(WARNING) << "Invalid device access permission: No device name.";
        continue;
      }

      uint16_t vendor_id;
      uint16_t product_id;

      if (!GetUint16FromDict(access_permission, kUsbVendorId, &vendor_id) ||
          !GetUint16FromDict(access_permission, kUsbProductId, &product_id)) {
        LOG(WARNING) << "Invalid device access permission: Invalid vendor_id "
                        "and/or product_id.";
        continue;
      }

      usb_access_permission_dict_.emplace(
          package, UsbDeviceEntry(std::string() /*guid*/,
                                  base::UTF8ToUTF16(*device_name),
                                  base::UTF8ToUTF16(*serial_number), vendor_id,
                                  product_id));
    }
  }
}

void ArcUsbHostPermissionManager::RequestUsbScanDeviceListPermission(
    const std::string& package_name,
    ArcUsbHostUiDelegate::RequestPermissionCallback callback) {
  if (HasUsbScanDeviceListPermission(package_name)) {
    std::move(callback).Run(true);
    return;
  }

  // Return ASAP to stop app from ANR. If granted, the permission will apply
  // when next time app tries to get device list.
  std::move(callback).Run(false);
  pending_requests_.emplace_back(
      ArcUsbHostPermissionManager::UsbPermissionRequest(
          package_name, true /*is_scan_request*/,
          std::nullopt /*usb_device_entry*/, std::nullopt /*callback*/));
  MaybeProcessNextPermissionRequest();
}

void ArcUsbHostPermissionManager::RequestUsbAccessPermission(
    const std::string& package_name,
    const std::string& guid,
    const std::u16string& serial_number,
    const std::u16string& manufacturer_string,
    const std::u16string& product_string,
    uint16_t vendor_id,
    uint16_t product_id,
    ArcUsbHostUiDelegate::RequestPermissionCallback callback) {
  UsbDeviceEntry usb_device_entry(
      guid,
      extensions::DevicePermissionsManager::GetPermissionMessage(
          vendor_id, product_id, manufacturer_string, product_string,
          serial_number, true /*always_include_manufacturer*/),
      serial_number, vendor_id, product_id);

  if (HasUsbAccessPermission(package_name, usb_device_entry)) {
    std::move(callback).Run(true);
    return;
  }
  pending_requests_.emplace_back(
      ArcUsbHostPermissionManager::UsbPermissionRequest(
          package_name, false /*is_scan_request*/, std::move(usb_device_entry),
          std::move(callback)));
  MaybeProcessNextPermissionRequest();
}

bool ArcUsbHostPermissionManager::HasUsbAccessPermission(
    const std::string& package_name,
    const std::string& guid,
    const std::u16string& serial_number,
    uint16_t vendor_id,
    uint16_t product_id) const {
  UsbDeviceEntry usb_device_entry(guid, std::u16string() /*device_name*/,
                                  serial_number, vendor_id, product_id);
  return HasUsbAccessPermission(package_name, usb_device_entry);
}

void ArcUsbHostPermissionManager::GrantUsbAccessPermission(
    const std::string& package_name,
    const std::string& guid,
    uint16_t vendor_id,
    uint16_t product_id) {
  // Create non-persistent usb device entry with empty serial_number.
  UsbDeviceEntry usb_device_entry(guid, std::u16string() /*device_name*/,
                                  std::u16string() /*serial_number*/, vendor_id,
                                  product_id);
  DCHECK(!usb_device_entry.IsPersistent());
  UpdateArcUsbAccessPermission(package_name, usb_device_entry,
                               true /*allowed*/);
}

std::unordered_set<std::string>
ArcUsbHostPermissionManager::GetEventPackageList(
    const std::string& guid,
    const std::u16string& serial_number,
    uint16_t vendor_id,
    uint16_t product_id) const {
  // Packages with USB device scan permission should receive USB events.
  std::unordered_set<std::string> event_packages(
      usb_scan_devicelist_permission_packages_);

  // Packages have USB access permission to this device should receive USB
  // events for this USB device.
  UsbDeviceEntry usb_device_entry(guid, std::u16string() /*device_name*/,
                                  serial_number, vendor_id, product_id);
  for (const auto& entry : usb_access_permission_dict_) {
    if (entry.second.Matches(usb_device_entry))
      event_packages.emplace(entry.first);
  }
  return event_packages;
}

void ArcUsbHostPermissionManager::DeviceRemoved(const std::string& guid) {
  // Remove pending requests.
  std::erase_if(pending_requests_,
                [guid](const UsbPermissionRequest& usb_permission_request) {
                  return !usb_permission_request.is_scan_request() &&
                         usb_permission_request.usb_device_entry()->guid ==
                             guid;
                });
  // Remove runtime permissions.
  for (auto iter = usb_access_permission_dict_.begin();
       iter != usb_access_permission_dict_.end();) {
    UsbDeviceEntry& usb_device_entry = iter->second;
    if (!usb_device_entry.IsPersistent() && usb_device_entry.guid == guid)
      iter = usb_access_permission_dict_.erase(iter);
    else
      ++iter;
  }
  if (current_requesting_guid_ == guid)
    current_requesting_guid_.clear();
}

void ArcUsbHostPermissionManager::OnPackageRemoved(
    const std::string& package_name,
    bool uninstalled) {
  // Remove pending requests.
  std::erase_if(
      pending_requests_,
      [package_name](const UsbPermissionRequest& usb_permission_request) {
        return usb_permission_request.package_name() == package_name;
      });
  // Remove runtime permissions.
  usb_scan_devicelist_permission_packages_.erase(package_name);
  usb_access_permission_dict_.erase(package_name);
  if (current_requesting_package_ == package_name)
    current_requesting_package_.clear();
}

void ArcUsbHostPermissionManager::MaybeProcessNextPermissionRequest() {
  if (is_permission_dialog_visible_ || pending_requests_.empty())
    return;

  is_permission_dialog_visible_ = true;

  auto current_request = std::move(pending_requests_.front());
  pending_requests_.erase(pending_requests_.begin());
  current_requesting_package_ = current_request.package_name();

  if (current_request.is_scan_request())
    current_requesting_guid_.clear();
  else
    current_requesting_guid_ = current_request.usb_device_entry()->guid;

  const std::string app_id =
      GetAppIdFromPackageName(current_requesting_package_, arc_app_list_prefs_);
  // App is uninstalled during the process.
  if (app_id.empty()) {
    OnUsbPermissionReceived(std::move(current_request), false);
    return;
  }

  const std::string app_name = arc_app_list_prefs_->GetApp(app_id)->name;

  if (current_request.is_scan_request()) {
    if (HasUsbScanDeviceListPermission(current_requesting_package_)) {
      OnUsbPermissionReceived(std::move(current_request), true);
    } else {
      ShowUsbScanDeviceListPermissionDialog(
          profile_, app_id,
          base::BindOnce(&ArcUsbHostPermissionManager::OnUsbPermissionReceived,
                         weak_ptr_factory_.GetWeakPtr(),
                         std::move(current_request)));
    }
  } else {
    if (HasUsbAccessPermission(current_requesting_package_,
                               *current_request.usb_device_entry())) {
      OnUsbPermissionReceived(std::move(current_request), true);
    } else {
      const std::u16string device_name =
          current_request.usb_device_entry()->device_name;
      ShowUsbAccessPermissionDialog(
          profile_, app_id, device_name,
          base::BindOnce(&ArcUsbHostPermissionManager::OnUsbPermissionReceived,
                         weak_ptr_factory_.GetWeakPtr(),
                         std::move(current_request)));
    }
  }
}

bool ArcUsbHostPermissionManager::HasUsbScanDeviceListPermission(
    const std::string& package_name) const {
  return usb_scan_devicelist_permission_packages_.count(package_name);
}

bool ArcUsbHostPermissionManager::HasUsbAccessPermission(
    const std::string& package_name,
    const UsbDeviceEntry& usb_device_entry) const {
  auto range = usb_access_permission_dict_.equal_range(package_name);
  for (auto iter = range.first; iter != range.second; iter++) {
    if (iter->second.Matches(usb_device_entry))
      return true;
  }
  return false;
}

void ArcUsbHostPermissionManager::ClearPermissionRequests() {
  pending_requests_.clear();
  current_requesting_package_.clear();
  current_requesting_guid_.clear();
  is_permission_dialog_visible_ = false;
}

void ArcUsbHostPermissionManager::OnUsbPermissionReceived(
    UsbPermissionRequest request,
    bool allowed) {
  is_permission_dialog_visible_ = false;

  const std::string& package_name = request.package_name();

  // If the package can is uninstalled while user clicks permission UI dialog.
  // Ignore current callback as it's outdated.
  if (package_name != current_requesting_package_) {
    MaybeProcessNextPermissionRequest();
    return;
  }

  // If the USB device is removed while user clicks permission UI dialog.
  // Ignore current callback as it's outdated.
  if (!request.is_scan_request() &&
      request.usb_device_entry()->guid != current_requesting_guid_) {
    MaybeProcessNextPermissionRequest();
    return;
  }

  if (request.is_scan_request()) {
    UpdateArcUsbScanDeviceListPermission(package_name, allowed);
  } else {
    request.Resolve(allowed);
    UpdateArcUsbAccessPermission(package_name, *request.usb_device_entry(),
                                 allowed);
  }

  MaybeProcessNextPermissionRequest();
}

void ArcUsbHostPermissionManager::UpdateArcUsbScanDeviceListPermission(
    const std::string& package_name,
    bool allowed) {
  // Currently we don't keep denied request. But keep the option open.
  if (!allowed)
    return;

  const auto result =
      usb_scan_devicelist_permission_packages_.emplace(package_name);
  // Permission is already recored.
  if (!result.second)
    return;

  arc_app_list_prefs_->SetPackagePrefs(
      package_name, kUsbScanDeviceListPermission, base::Value(allowed));
}

void ArcUsbHostPermissionManager::UpdateArcUsbAccessPermission(
    const std::string& package_name,
    const UsbDeviceEntry& usb_device_entry,
    bool allowed) {
  // Currently we don't keep denied request. But keep the option open.
  if (!allowed)
    return;

  // Record already exists.
  if (HasUsbAccessPermission(package_name, usb_device_entry))
    return;

  usb_access_permission_dict_.emplace(
      std::make_pair(package_name, usb_device_entry));

  if (!usb_device_entry.IsPersistent())
    return;

  base::Value::Dict new_permission;
  new_permission.Set(kUsbSerialNumber, usb_device_entry.serial_number);
  new_permission.Set(kUsbDeviceName, usb_device_entry.device_name);
  new_permission.Set(kUsbVendorId, usb_device_entry.vendor_id);
  new_permission.Set(kUsbProductId, usb_device_entry.product_id);

  base::Value* access_permission_list_value =
      arc_app_list_prefs_->GetPackagePrefs(package_name, kUsbAccessPermission);
  if (!access_permission_list_value ||
      !access_permission_list_value->is_list()) {
    if (access_permission_list_value &&
        !access_permission_list_value->is_list()) {
      LOG(WARNING) << "Wrong base::Value::Type found for device access "
                      "permission list Chrome prefs. Will overwrite with "
                      "base::Value::Type::List";
    }
    arc_app_list_prefs_->SetPackagePrefs(package_name, kUsbAccessPermission,
                                         base::Value(base::Value::Type::LIST));
  }
  arc_app_list_prefs_->GetPackagePrefs(package_name, kUsbAccessPermission)
      ->GetList()
      .Append(std::move(new_permission));
}

void ArcUsbHostPermissionManager::ClearPermissionForTesting() {
  usb_scan_devicelist_permission_packages_.clear();
  usb_access_permission_dict_.clear();
}

}  // namespace arc