910e62b5创建于 1月15日历史提交
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/web_test/browser/web_test_permission_manager.h"

#include <functional>
#include <list>
#include <memory>
#include <utility>

#include "base/barrier_callback.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "content/browser/permissions/permission_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_result.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/web_test/browser/web_test_content_browser_client.h"
#include "net/base/schemeful_site.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom-forward.h"

namespace content {

namespace {

std::vector<ContentSettingPatternSource> GetContentSettings(
    const ContentSettingsPattern& permission_pattern,
    const ContentSettingsPattern& embedding_pattern,
    blink::mojom::PermissionStatus status) {
  std::optional<ContentSetting> setting;
  switch (status) {
    case blink::mojom::PermissionStatus::GRANTED:
      setting = ContentSetting::CONTENT_SETTING_ALLOW;
      break;
    case blink::mojom::PermissionStatus::DENIED:
      setting = ContentSetting::CONTENT_SETTING_BLOCK;
      break;
    case blink::mojom::PermissionStatus::ASK:
      break;
  }
  std::vector<ContentSettingPatternSource> patterns;
  if (setting) {
    patterns.emplace_back(permission_pattern, embedding_pattern,
                          base::Value(*setting),
                          content_settings::ProviderType::kNone,
                          /*incognito=*/false);
  }
  return patterns;
}

bool ShouldHideDeniedState(blink::PermissionType permission_type) {
  return permission_type == blink::PermissionType::STORAGE_ACCESS_GRANT ||
         permission_type == blink::PermissionType::TOP_LEVEL_STORAGE_ACCESS;
}

}  // namespace

WebTestPermissionManager::PermissionDescription::PermissionDescription() =
    default;
WebTestPermissionManager::PermissionDescription::PermissionDescription(
    blink::mojom::PermissionDescriptorPtr permission_descriptor,
    const GURL& origin,
    const GURL& embedding_origin)
    : permission_descriptor(std::move(permission_descriptor)),
      origin(origin),
      embedding_origin(embedding_origin) {}

WebTestPermissionManager::PermissionDescription::PermissionDescription(
    const PermissionDescription& other) {
  permission_descriptor = other.permission_descriptor->Clone();
  origin = other.origin;
  embedding_origin = other.embedding_origin;
}

WebTestPermissionManager::PermissionDescription::~PermissionDescription() =
    default;

bool WebTestPermissionManager::PermissionDescription::operator==(
    const PermissionDescription& other) const {
  blink::PermissionType permission_type =
      blink::PermissionDescriptorToPermissionType(permission_descriptor);
  if (permission_type != blink::PermissionDescriptorToPermissionType(
                             other.permission_descriptor)) {
    return false;
  }

  if (permission_type == blink::PermissionType::STORAGE_ACCESS_GRANT) {
    const net::SchemefulSite requesting_site(origin);
    const net::SchemefulSite other_requesting_site(other.origin);
    const net::SchemefulSite embedding_site(embedding_origin);
    const net::SchemefulSite other_embedding_site(other.embedding_origin);
    return requesting_site == other_requesting_site &&
           embedding_site == other_embedding_site;
  }

  return origin == other.origin && embedding_origin == other.embedding_origin;
}

bool WebTestPermissionManager::PermissionDescription::operator==(
    PermissionResultSubscription* other) const {
  if (permission_descriptor != other->permission_descriptor) {
    return false;
  }

  if (blink::PermissionDescriptorToPermissionType(permission_descriptor) ==
      blink::PermissionType::STORAGE_ACCESS_GRANT) {
    const net::SchemefulSite requesting_site(origin);
    const net::SchemefulSite other_requesting_site(
        other->requesting_origin_delegation);
    const net::SchemefulSite embedding_site(embedding_origin);
    const net::SchemefulSite other_embedding_site(other->embedding_origin);
    return requesting_site == other_requesting_site &&
           embedding_site == other_embedding_site;
  }

  return origin == other->requesting_origin_delegation &&
         embedding_origin == other->embedding_origin;
}

size_t WebTestPermissionManager::PermissionDescription::Hash::operator()(
    const PermissionDescription& description) const {
  const int type_int =
      static_cast<int>(blink::PermissionDescriptorToPermissionType(
          description.permission_descriptor));

  if (blink::PermissionDescriptorToPermissionType(
          description.permission_descriptor) ==
      blink::PermissionType::STORAGE_ACCESS_GRANT) {
    const net::SchemefulSite requesting_site(description.origin);
    const net::SchemefulSite embedding_site(description.embedding_origin);
    const size_t hash =
        base::HashInts(type_int, base::FastHash(embedding_site.Serialize()));
    return base::HashInts(hash, base::FastHash(requesting_site.Serialize()));
  }

  const size_t hash = base::HashInts(
      type_int, base::FastHash(description.embedding_origin.spec()));
  return base::HashInts(hash, base::FastHash(description.origin.spec()));
}

WebTestPermissionManager::WebTestPermissionManager(
    BrowserContext& browser_context)
    : PermissionControllerDelegate(), browser_context_(browser_context) {}

WebTestPermissionManager::~WebTestPermissionManager() = default;

void WebTestPermissionManager::RequestPermissions(
    content::RenderFrameHost* render_frame_host,
    const content::PermissionRequestDescription& request_description,
    base::OnceCallback<void(const std::vector<content::PermissionResult>&)>
        callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (render_frame_host->IsNestedWithinFencedFrame()) {
    std::move(callback).Run(std::vector<content::PermissionResult>(
        request_description.permissions.size(),
        PermissionResult(blink::mojom::PermissionStatus::DENIED,
                         PermissionStatusSource::FENCED_FRAME)));
    return;
  }

  std::vector<PermissionResult> result;
  result.reserve(request_description.permissions.size());
  const GURL& embedding_origin = PermissionUtil::GetLastCommittedOriginAsURL(
      render_frame_host->GetMainFrame());
  for (const auto& permission : request_description.permissions) {
    result.emplace_back(GetPermissionStatusForRequestPermission(
        permission, request_description.requesting_origin, embedding_origin));
  }

  std::move(callback).Run(result);
}

void WebTestPermissionManager::ResetPermission(blink::PermissionType permission,
                                               const GURL& requesting_origin,
                                               const GURL& embedding_origin) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  base::AutoLock lock(permissions_lock_);

  const auto key = PermissionDescription(
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          permission),
      requesting_origin, embedding_origin);
  if (!base::Contains(permissions_, key)) {
    return;
  }
  permissions_.erase(key);
}

void WebTestPermissionManager::RequestPermissionsFromCurrentDocument(
    content::RenderFrameHost* render_frame_host,
    const content::PermissionRequestDescription& request_description,
    base::OnceCallback<void(const std::vector<PermissionResult>&)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (render_frame_host->IsNestedWithinFencedFrame()) {
    std::move(callback).Run(std::vector<PermissionResult>(
        request_description.permissions.size(),
        PermissionResult(blink::mojom::PermissionStatus::DENIED)));
    return;
  }

  std::vector<PermissionResult> result;
  result.reserve(request_description.permissions.size());
  const GURL& embedding_origin = PermissionUtil::GetLastCommittedOriginAsURL(
      render_frame_host->GetMainFrame());
  for (const auto& permission : request_description.permissions) {
    result.emplace_back(GetPermissionStatusForRequestPermission(
        permission, request_description.requesting_origin, embedding_origin));
  }

  std::move(callback).Run(result);
}

blink::mojom::PermissionStatus
WebTestPermissionManager::GetPermissionStatusForRequestPermission(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    const GURL& requesting_origin,
    const GURL& embedding_origin) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
         BrowserThread::CurrentlyOn(BrowserThread::IO));

  const auto permission_type =
      blink::PermissionDescriptorToPermissionType(permission_descriptor);

  // The same-site auto-grant mechanism for STORAGE_ACCESS_GRANT currently only
  // works when requesting permissions.
  // TODO(crbug.com/40278136): maybe it should also work when querying
  // permissions.
  if (permission_type == blink::PermissionType::STORAGE_ACCESS_GRANT &&
      (requesting_origin == embedding_origin ||
       net::SchemefulSite(requesting_origin) ==
           net::SchemefulSite(embedding_origin))) {
    return blink::mojom::PermissionStatus::GRANTED;
  }

  return GetPermissionStatus(permission_descriptor, requesting_origin,
                             embedding_origin);
}

blink::mojom::PermissionStatus WebTestPermissionManager::GetPermissionStatus(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    const GURL& requesting_origin,
    const GURL& embedding_origin) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
         BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::AutoLock lock(permissions_lock_);
  const auto permission_type =
      blink::PermissionDescriptorToPermissionType(permission_descriptor);

  auto it = permissions_.find(PermissionDescription(
      permission_descriptor->Clone(), requesting_origin, embedding_origin));
  if (it == permissions_.end()) {
    auto default_state = default_permission_status_.find(permission_type);
    if (default_state != default_permission_status_.end()) {
      return default_state->second;
    }
    return blink::mojom::PermissionStatus::DENIED;
  }

  // Immitates the behaviour of the NotificationPermissionContext in that
  // permission cannot be requested from cross-origin iframes, which the current
  // permission status should reflect when it's status is ASK.
  if (permission_type == blink::PermissionType::NOTIFICATIONS) {
    if (requesting_origin != embedding_origin &&
        it->second == blink::mojom::PermissionStatus::ASK) {
      return blink::mojom::PermissionStatus::DENIED;
    }
  }

  // Some permissions (currently storage access related) do not expose the
  // denied state to avoid exposing potentially private user choices to
  // developers.
  if (ShouldHideDeniedState(permission_type) &&
      it->second == blink::mojom::PermissionStatus::DENIED) {
    return blink::mojom::PermissionStatus::ASK;
  }

  return it->second;
}

PermissionResult
WebTestPermissionManager::GetPermissionResultForOriginWithoutContext(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    const url::Origin& requesting_origin,
    const url::Origin& embedding_origin) {
  // If permissions start relying on permission setting states (instead of
  // permission status only) for web tests, the permission setting will need to
  // be set in the permission result.
  return PermissionResult(GetPermissionStatus(permission_descriptor,
                                              requesting_origin.GetURL(),
                                              embedding_origin.GetURL()));
}

PermissionResult
WebTestPermissionManager::GetPermissionResultForCurrentDocument(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    RenderFrameHost* render_frame_host,
    bool should_include_device_status) {
  if (render_frame_host->IsNestedWithinFencedFrame())
    return PermissionResult(blink::mojom::PermissionStatus::DENIED);
  // If permissions start relying on permission setting states (instead of
  // permission status only) for web tests, the permission setting will need to
  // be set in the permission result.
  return PermissionResult(GetPermissionStatus(
      permission_descriptor,
      PermissionUtil::GetLastCommittedOriginAsURL(render_frame_host),
      PermissionUtil::GetLastCommittedOriginAsURL(
          render_frame_host->GetMainFrame())));
}

PermissionResult WebTestPermissionManager::GetPermissionResultForWorker(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    RenderProcessHost* render_process_host,
    const GURL& worker_origin) {
  // If permissions start relying on permission setting states (instead of
  // permission status only) for web tests, the permission setting will need to
  // be set in the permission result.
  return PermissionResult(
      GetPermissionStatus(permission_descriptor, worker_origin, worker_origin));
}

PermissionResult
WebTestPermissionManager::GetPermissionResultForEmbeddedRequester(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    content::RenderFrameHost* render_frame_host,
    const url::Origin& overridden_origin) {
  if (render_frame_host->IsNestedWithinFencedFrame()) {
    return PermissionResult(blink::mojom::PermissionStatus::DENIED,
                            PermissionStatusSource::FENCED_FRAME);
  }
  // If permissions start relying on permission setting states (instead of
  // permission status only) for web tests, the permission setting will need to
  // be set in the permission result.
  return PermissionResult(
      GetPermissionStatus(permission_descriptor, overridden_origin.GetURL(),
                          PermissionUtil::GetLastCommittedOriginAsURL(
                              render_frame_host->GetMainFrame())));
}

void WebTestPermissionManager::OnPermissionStatusChangeSubscriptionAdded(
    content::PermissionController::SubscriptionId subscription_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!subscriptions() || subscriptions()->IsEmpty()) {
    return;
  }
  content::PermissionResultSubscription* subscription =
      subscriptions()->Lookup(subscription_id);
  if (!subscription) {
    return;
  }

  // If the request is from a worker, it won't have a RFH.
  GURL embedding_origin = subscription->requesting_origin;
  if (subscription->render_frame_id != -1) {
    subscription->embedding_origin = embedding_origin =
        PermissionUtil::GetLastCommittedOriginAsURL(
            content::RenderFrameHost::FromID(subscription->render_process_id,
                                             subscription->render_frame_id)
                ->GetMainFrame());
  }
  subscription->requesting_origin_delegation = subscription->requesting_origin;
  // If permissions start relying on permission setting states (instead of
  // permission status only) for web tests, the permission setting will need to
  // be set in the permission result.
  subscription->permission_result = PermissionResult(GetPermissionStatus(
      subscription->permission_descriptor, subscription->requesting_origin,
      subscription->embedding_origin));
}

void WebTestPermissionManager::UnsubscribeFromPermissionResultChange(
    content::PermissionController::SubscriptionId subscription_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

void WebTestPermissionManager::SetPermission(
    blink::PermissionType permission,
    blink::mojom::PermissionStatus status,
    const GURL& url,
    const GURL& embedding_url,
    blink::test::mojom::PermissionAutomation::SetPermissionCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  PermissionDescription description(
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          permission),
      url.DeprecatedGetOriginAsURL(), embedding_url.DeprecatedGetOriginAsURL());

  {
    base::AutoLock lock(permissions_lock_);

    auto it = permissions_.find(description);
    if (it == permissions_.end()) {
      permissions_.insert(
          std::pair<const PermissionDescription,
                    blink::mojom::PermissionStatus>(description, status));
    } else {
      it->second = status;
    }
  }

  OnPermissionChanged(description, PermissionResult(status),
                      std::move(callback));
}

void WebTestPermissionManager::SetPermission(
    blink::mojom::PermissionDescriptorPtr descriptor,
    blink::mojom::PermissionStatus status,
    const GURL& url,
    const GURL& embedding_url,
    blink::test::mojom::PermissionAutomation::SetPermissionCallback callback) {
  auto type = blink::MaybePermissionDescriptorToPermissionType(descriptor);
  if (!type) {
    std::move(callback).Run(false);
    return;
  }
  GURL applicable_permission_url = url;
  if (PermissionUtil::IsDomainOverride(descriptor)) {
    const auto overridden_origin =
        PermissionUtil::ExtractDomainOverride(descriptor);
    applicable_permission_url = overridden_origin.GetURL();
  }

  SetPermission(*type, status, applicable_permission_url, embedding_url,
                std::move(callback));
}

void WebTestPermissionManager::ResetPermissions() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  base::AutoLock lock(permissions_lock_);
  permissions_.clear();
}

void WebTestPermissionManager::Bind(
    mojo::PendingReceiver<blink::test::mojom::PermissionAutomation> receiver) {
  receivers_.Add(this, std::move(receiver));
}

void WebTestPermissionManager::OnPermissionChanged(
    const PermissionDescription& permission,
    PermissionResult permission_result,
    blink::test::mojom::PermissionAutomation::SetPermissionCallback
        permission_callback) {
  if (!subscriptions()) {
    return;
  }

  std::vector<base::OnceClosure> callbacks;
  callbacks.reserve(subscriptions()->size());

  for (content::PermissionController::SubscriptionsMap::iterator iter(
           subscriptions());
       !iter.IsAtEnd(); iter.Advance()) {
    PermissionResultSubscription* subscription = iter.GetCurrentValue();
    if (permission != subscription) {
      continue;
    }

    if (subscription->permission_result &&
        subscription->permission_result->status == permission_result.status &&
        subscription->permission_result->retrieved_permission_setting ==
            permission_result.retrieved_permission_setting) {
      continue;
    }

    subscription->permission_result = permission_result;

    // Add the callback to |callbacks| which will be run after the loop to
    // prevent re-entrance issues.
    callbacks.push_back(base::BindOnce(subscription->callback,
                                       permission_result,
                                       /*ignore_status_override=*/false));
  }

  for (auto& callback : callbacks)
    std::move(callback).Run();

  // The network service expects to hear about any new storage-access permission
  // grants, so we have to inform it. This is true for "regular" or top-level
  // storage access permission changes.
  switch (blink::PermissionDescriptorToPermissionType(
      permission.permission_descriptor)) {
    case blink::PermissionType::STORAGE_ACCESS_GRANT:
      browser_context_->GetDefaultStoragePartition()
          ->GetCookieManagerForBrowserProcess()
          ->SetContentSettings(
              ContentSettingsType::STORAGE_ACCESS,
              GetContentSettings(
                  ContentSettingsPattern::FromURLToSchemefulSitePattern(
                      permission.origin),
                  ContentSettingsPattern::FromURLToSchemefulSitePattern(
                      permission.embedding_origin),
                  permission_result.status),
              base::BindOnce(std::move(permission_callback), /*success=*/true));
      break;
    case blink::PermissionType::TOP_LEVEL_STORAGE_ACCESS: {
      // We dual-write `TOP_LEVEL_STORAGE_ACCESS` and `STORAGE_ACCESS_GRANT` due
      // to the former granting a superset of the latter. Accordingly, we wait
      // until both permissions have been written, including the notification to
      // the network service, to run the permission callback. This could happen
      // in either order without issue, so a barrier callback is used to ensure
      // whichever finishes last then runs the callback. The asynchronicity
      // comes in the form of the updates to the network service.
      auto barrier_callback = base::BarrierCallback<bool>(
          /*num_callbacks=*/3,
          base::BindOnce(
              [](blink::test::mojom::PermissionAutomation::SetPermissionCallback
                     permission_callback,
                 const std::vector<bool>& successes) {
                std::move(permission_callback)
                    .Run(std::ranges::all_of(successes, std::identity()));
              },
              std::move(permission_callback)));
      SetPermission(blink::PermissionType::STORAGE_ACCESS_GRANT,
                    blink::mojom::PermissionStatus::GRANTED, permission.origin,
                    permission.embedding_origin, barrier_callback);

      auto* cookie_manager = browser_context_->GetDefaultStoragePartition()
                                 ->GetCookieManagerForBrowserProcess();

      cookie_manager->SetContentSettings(
          ContentSettingsType::STORAGE_ACCESS,
          GetContentSettings(
              ContentSettingsPattern::FromURL(permission.origin),
              ContentSettingsPattern::FromURL(permission.embedding_origin),
              permission_result.status),
          base::BindOnce(barrier_callback, true));

      cookie_manager->SetContentSettings(
          ContentSettingsType::TOP_LEVEL_STORAGE_ACCESS,
          GetContentSettings(
              ContentSettingsPattern::FromURL(permission.origin),
              ContentSettingsPattern::FromURL(permission.embedding_origin),
              permission_result.status),
          base::BindOnce(barrier_callback, true));

      break;
    }
    default:
      std::move(permission_callback).Run(true);
      break;
  }
}

}  // namespace content