chromium/chrome/browser/smart_card/smart_card_permission_context.cc

// Copyright 2024 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/smart_card/smart_card_permission_context.h"

#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/one_time_permissions_tracker.h"
#include "chrome/browser/permissions/one_time_permissions_tracker_factory.h"
#include "chrome/browser/permissions/one_time_permissions_tracker_observer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/smart_card/smart_card_reader_tracker.h"
#include "chrome/browser/smart_card/smart_card_reader_tracker_factory.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"

namespace {
constexpr char kReaderNameKey[] = "reader-name";

static base::Value::Dict ReaderNameToValue(const std::string& reader_name) {
  base::Value::Dict value;
  value.Set(kReaderNameKey, reader_name);
  return value;
}

}  // anonymous namespace

class SmartCardPermissionContext::OneTimeObserver
    : public OneTimePermissionsTrackerObserver {
 public:
  explicit OneTimeObserver(SmartCardPermissionContext& permission_context)
      : permission_context_(permission_context) {
    observation_.Observe(OneTimePermissionsTrackerFactory::GetForBrowserContext(
        &permission_context.profile_.get()));
  }
  void OnLastPageFromOriginClosed(const url::Origin& origin) override {
    permission_context_->RevokeEphemeralPermissionsForOrigin(origin);
  }

 private:
  base::ScopedObservation<OneTimePermissionsTracker,
                          OneTimePermissionsTrackerObserver>
      observation_{this};
  base::raw_ref<SmartCardPermissionContext> permission_context_;
};

class SmartCardPermissionContext::PowerSuspendObserver
    : public base::PowerSuspendObserver {
 public:
  explicit PowerSuspendObserver(SmartCardPermissionContext& permission_context)
      : permission_context_(permission_context) {
    base::PowerMonitor::AddPowerSuspendObserver(this);
  }

  ~PowerSuspendObserver() override {
    base::PowerMonitor::RemovePowerSuspendObserver(this);
  }

  void OnSuspend() override {
    permission_context_->RevokeEphemeralPermissions();
  }

 private:
  base::raw_ref<SmartCardPermissionContext> permission_context_;
};

class SmartCardPermissionContext::ReaderObserver
    : public SmartCardReaderTracker::Observer {
 public:
  explicit ReaderObserver(SmartCardPermissionContext& permission_context)
      : permission_context_(permission_context) {}

  void Reset(std::vector<SmartCardReaderTracker::ReaderInfo> info_list) {
    known_info_map_.clear();
    for (SmartCardReaderTracker::ReaderInfo& info : info_list) {
      std::string name = info.name;
      known_info_map_.emplace(std::move(name), std::move(info));
    }
  }

  void OnReaderRemoved(const std::string& reader_name) override {
    known_info_map_.erase(reader_name);
    permission_context_->RevokeEphemeralPermissionsForReader(reader_name);
  }

  void OnReaderChanged(
      const SmartCardReaderTracker::ReaderInfo& reader_info) override {
    // Compare the new reader state with its previous one to find out whether
    // its card has been removed. If so, notify the PermissionContext.

    auto it = known_info_map_.find(reader_info.name);
    if (it == known_info_map_.end()) {
      known_info_map_.emplace(reader_info.name, reader_info);
      return;
    }

    SmartCardReaderTracker::ReaderInfo& known_info = it->second;

    // Either the card was removed or there was both a removal and an insertion
    // in the meantime (meaning that reader_info.present could still be true).
    // Note that event_count is not supported in all platforms.
    const bool card_removed =
        known_info.present &&
        (!reader_info.present ||
         (known_info.event_count < reader_info.event_count));

    // Update known_info.
    known_info = reader_info;

    if (card_removed) {
      permission_context_->RevokeEphemeralPermissionsForReader(
          reader_info.name);
    }
  }

  std::map<std::string, SmartCardReaderTracker::ReaderInfo> known_info_map_;
  base::raw_ref<SmartCardPermissionContext> permission_context_;
};

SmartCardPermissionContext::SmartCardPermissionContext(Profile* profile)
    : ObjectPermissionContextBase(
          ContentSettingsType::SMART_CARD_GUARD,
          ContentSettingsType::SMART_CARD_DATA,
          HostContentSettingsMapFactory::GetForProfile(profile)),
      reader_observer_(std::make_unique<ReaderObserver>(*this)),
      profile_(*profile),
      weak_ptr_factory_(this) {}

SmartCardPermissionContext::~SmartCardPermissionContext() = default;

std::string SmartCardPermissionContext::GetKeyForObject(
    const base::Value::Dict& object) {
  if (!IsValidObject(object)) {
    return std::string();
  }

  return *object.FindString(kReaderNameKey);
}

bool SmartCardPermissionContext::HasReaderPermission(
    content::RenderFrameHost& render_frame_host,
    const std::string& reader_name) {
  return HasReaderPermission(
      render_frame_host.GetMainFrame()->GetLastCommittedOrigin(), reader_name);
}

bool SmartCardPermissionContext::HasReaderPermission(
    const url::Origin& origin,
    const std::string& reader_name) {
  if (!CanRequestObjectPermission(origin)) {
    return false;
  }

  return ephemeral_grants_[origin].contains(reader_name) ||
         HasPersistentReaderPermission(origin, reader_name);
}

void SmartCardPermissionContext::RequestReaderPermisssion(
    content::RenderFrameHost& render_frame_host,
    const std::string& reader_name,
    RequestReaderPermissionCallback callback) {
  const url::Origin& origin =
      render_frame_host.GetMainFrame()->GetLastCommittedOrigin();

  auto* web_contents =
      content::WebContents::FromRenderFrameHost(&render_frame_host);

  permissions::PermissionRequestManager* permission_request_manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents);
  if (!permission_request_manager) {
    LOG(ERROR) << "Cannot request permission: no PermissionRequestManager";
    std::move(callback).Run(false);
    return;
  }

  if (!CanRequestObjectPermission(origin)) {
    std::move(callback).Run(false);
    return;
  }

  // Regarding ownership: The request will delete itself once the request
  // manager notifies that it can do so.
  auto* permission_request = new SmartCardPermissionRequest(
      origin, reader_name,
      base::BindOnce(&SmartCardPermissionContext::OnPermissionRequestDecided,
                     weak_ptr_factory_.GetWeakPtr(), origin, reader_name,
                     std::move(callback)));

  permission_request_manager->AddRequest(&render_frame_host,
                                         permission_request);
}

void SmartCardPermissionContext::GrantEphemeralReaderPermission(
    const url::Origin& origin,
    const std::string& reader_name) {
  CHECK(!HasReaderPermission(origin, reader_name));
  ephemeral_grants_[origin].insert(reader_name);

  if (!power_suspend_observer_) {
    power_suspend_observer_ = std::make_unique<PowerSuspendObserver>(*this);
  }

  if (!one_time_observer_) {
    one_time_observer_ = std::make_unique<OneTimeObserver>(*this);
  }

  GetReaderTracker().Start(
      reader_observer_.get(),
      base::BindOnce(&SmartCardPermissionContext::OnTrackingStarted,
                     weak_ptr_factory_.GetWeakPtr()));
}

void SmartCardPermissionContext::GrantPersistentReaderPermission(
    const url::Origin& origin,
    const std::string& reader_name) {
  CHECK(!HasReaderPermission(origin, reader_name));
  GrantObjectPermission(origin, ReaderNameToValue(reader_name));
}

bool SmartCardPermissionContext::IsValidObject(
    const base::Value::Dict& object) {
  if (object.size() != 1) {
    return false;
  }

  const std::string* reader_name = object.FindString(kReaderNameKey);
  return reader_name && !reader_name->empty();
}

std::u16string SmartCardPermissionContext::GetObjectDisplayName(
    const base::Value::Dict& object) {
  const std::string* reader_name = object.FindString(kReaderNameKey);
  CHECK(reader_name);
  return base::UTF8ToUTF16(*reader_name);
}

bool SmartCardPermissionContext::HasPersistentReaderPermission(
    const url::Origin& origin,
    const std::string& reader_name) {
  for (const auto& object :
       ObjectPermissionContextBase::GetGrantedObjects(origin)) {
    const base::Value::Dict& reader_value = object->value;

    // Objects provided by the parent class can be assumed valid.
    CHECK(IsValidObject(reader_value));

    if (reader_name != *reader_value.FindString(kReaderNameKey)) {
      continue;
    }

    return true;
  }
  return false;
}

void SmartCardPermissionContext::RevokeEphemeralPermissionsForReader(
    const std::string& reader_name) {
  for (auto it = ephemeral_grants_.begin(); it != ephemeral_grants_.end();) {
    std::set<std::string>& reader_set = it->second;

    reader_set.erase(reader_name);

    if (reader_set.empty()) {
      it = ephemeral_grants_.erase(it);
    } else {
      ++it;
    }
  }

  if (ephemeral_grants_.empty()) {
    StopObserving();
  }
}

void SmartCardPermissionContext::RevokeEphemeralPermissionsForOrigin(
    const url::Origin& origin) {
  ephemeral_grants_.erase(origin);

  if (ephemeral_grants_.empty()) {
    StopObserving();
  }
}

void SmartCardPermissionContext::RevokeEphemeralPermissions() {
  if (ephemeral_grants_.empty()) {
    return;
  }

  ephemeral_grants_.clear();
  StopObserving();
}

void SmartCardPermissionContext::OnTrackingStarted(
    std::optional<std::vector<SmartCardReaderTracker::ReaderInfo>> info_list) {
  if (!info_list) {
    // PC/SC failed on us.
    LOG(ERROR) << "Failed to start reader tracking.";
    return;
  }
  reader_observer_->Reset(std::move(*info_list));
}

void SmartCardPermissionContext::StopObserving() {
  GetReaderTracker().Stop(reader_observer_.get());
  one_time_observer_.reset();
  power_suspend_observer_.reset();
}

SmartCardReaderTracker& SmartCardPermissionContext::GetReaderTracker() const {
  return SmartCardReaderTrackerFactory::GetForProfile(*profile_);
}

void SmartCardPermissionContext::OnPermissionRequestDecided(
    const url::Origin& origin,
    const std::string& reader_name,
    RequestReaderPermissionCallback callback,
    SmartCardPermissionRequest::Result result) {
  switch (result) {
    case SmartCardPermissionRequest::Result::kAllowOnce:
      GrantEphemeralReaderPermission(origin, reader_name);
      std::move(callback).Run(true);
      break;
    case SmartCardPermissionRequest::Result::kAllowAlways:
      GrantPersistentReaderPermission(origin, reader_name);
      std::move(callback).Run(true);
      break;
    case SmartCardPermissionRequest::Result::kDontAllow:
      std::move(callback).Run(false);
      break;
  }
}