chromium/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc

// 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 "android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "android_webview/browser/aw_browser_context.h"
#include "android_webview/browser/aw_cookie_access_policy.h"
#include "android_webview/browser/cookie_manager.h"
#include "base/memory/ptr_util.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/cookies/parsed_cookie.h"
#include "net/storage_access_api/status.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom-forward.h"
#include "url/gurl.h"

namespace android_webview {

namespace {

using PrivacySetting = net::NetworkDelegate::PrivacySetting;

}  // namespace

class AwProxyingRestrictedCookieManagerListener
    : public network::mojom::CookieChangeListener {
 public:
  AwProxyingRestrictedCookieManagerListener(
      const GURL& url,
      const net::SiteForCookies& site_for_cookies,
      base::WeakPtr<AwProxyingRestrictedCookieManager>
          aw_restricted_cookie_manager,
      mojo::PendingRemote<network::mojom::CookieChangeListener> client_listener,
      net::StorageAccessApiStatus storage_access_api_status)
      : url_(url),
        site_for_cookies_(site_for_cookies),
        storage_access_api_status_(storage_access_api_status),
        aw_restricted_cookie_manager_(aw_restricted_cookie_manager),
        client_listener_(std::move(client_listener)) {}

  void OnCookieChange(const net::CookieChangeInfo& change) override {
    if (aw_restricted_cookie_manager_) {
      PrivacySetting cookieState = aw_restricted_cookie_manager_->AllowCookies(
          url_, site_for_cookies_, storage_access_api_status_);

      if (cookieState == PrivacySetting::kStateAllowed ||
          (cookieState == PrivacySetting::kPartitionedStateAllowedOnly &&
           change.cookie.IsPartitioned())) {
        client_listener_->OnCookieChange(change);
      }
    }
  }

 private:
  const GURL url_;
  const net::SiteForCookies site_for_cookies_;
  // restricted_cookie_manager in services/network follows a similar pattern of
  // using the state of "storage_access_api_status" at the time of the listener
  // being added so we are matching that behaviour. If the storage access was
  // enabled _after_ the listener was added, it will not be updated here.
  net::StorageAccessApiStatus storage_access_api_status_;
  base::WeakPtr<AwProxyingRestrictedCookieManager>
      aw_restricted_cookie_manager_;
  mojo::Remote<network::mojom::CookieChangeListener> client_listener_;
};

// static
void AwProxyingRestrictedCookieManager::CreateAndBind(
    mojo::PendingRemote<network::mojom::RestrictedCookieManager> underlying_rcm,
    bool is_service_worker,
    int process_id,
    int frame_id,
    mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
    AwCookieAccessPolicy* aw_cookie_access_policy) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::optional<content::GlobalRenderFrameHostToken> frame_token;
  if (!is_service_worker) {
    if (auto* rfh = content::RenderFrameHost::FromID(process_id, frame_id)) {
      frame_token = rfh->GetGlobalFrameToken();
    }
  }

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &AwProxyingRestrictedCookieManager::CreateAndBindOnIoThread,
          std::move(underlying_rcm), is_service_worker, frame_token,
          std::move(receiver), aw_cookie_access_policy));
}

AwProxyingRestrictedCookieManager::~AwProxyingRestrictedCookieManager() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}

void AwProxyingRestrictedCookieManager::GetAllForUrl(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_frame_origin,
    net::StorageAccessApiStatus storage_access_api_status,
    network::mojom::CookieManagerGetOptionsPtr options,
    bool is_ad_tagged,
    bool force_disable_third_party_cookies,
    GetAllForUrlCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  PrivacySetting cookieState =
      AllowCookies(url, site_for_cookies, storage_access_api_status);

  if (cookieState == PrivacySetting::kStateDisallowed) {
    std::move(callback).Run(std::vector<net::CookieWithAccessResult>());
    return;
  }

  bool disable_3pcs =
      force_disable_third_party_cookies ||
      cookieState == PrivacySetting::kPartitionedStateAllowedOnly;

  underlying_restricted_cookie_manager_->GetAllForUrl(
      url, site_for_cookies, top_frame_origin, storage_access_api_status,
      std::move(options), is_ad_tagged, disable_3pcs, std::move(callback));
}

void AwProxyingRestrictedCookieManager::SetCanonicalCookie(
    const net::CanonicalCookie& cookie,
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_frame_origin,
    net::StorageAccessApiStatus storage_access_api_status,
    net::CookieInclusionStatus status,
    SetCanonicalCookieCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  PrivacySetting cookieState =
      AllowCookies(url, site_for_cookies, storage_access_api_status);

  if (cookieState == PrivacySetting::kStateDisallowed) {
    std::move(callback).Run(false);
    return;
  }

  if (cookie.IsPartitioned() || cookieState == PrivacySetting::kStateAllowed) {
    underlying_restricted_cookie_manager_->SetCanonicalCookie(
        cookie, url, site_for_cookies, top_frame_origin,
        storage_access_api_status, status, std::move(callback));
  } else {
    std::move(callback).Run(false);
  }
}

void AwProxyingRestrictedCookieManager::AddChangeListener(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_frame_origin,
    net::StorageAccessApiStatus storage_access_api_status,
    mojo::PendingRemote<network::mojom::CookieChangeListener> listener,
    AddChangeListenerCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  mojo::PendingRemote<network::mojom::CookieChangeListener>
      proxy_listener_remote;
  auto proxy_listener =
      std::make_unique<AwProxyingRestrictedCookieManagerListener>(
          url, site_for_cookies, weak_factory_.GetWeakPtr(),
          std::move(listener), storage_access_api_status);

  mojo::MakeSelfOwnedReceiver(
      std::move(proxy_listener),
      proxy_listener_remote.InitWithNewPipeAndPassReceiver());

  underlying_restricted_cookie_manager_->AddChangeListener(
      url, site_for_cookies, top_frame_origin, storage_access_api_status,
      std::move(proxy_listener_remote), std::move(callback));
}

void AwProxyingRestrictedCookieManager::SetCookieFromString(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_frame_origin,
    net::StorageAccessApiStatus storage_access_api_status,
    const std::string& cookie,
    SetCookieFromStringCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  PrivacySetting cookieState =
      AllowCookies(url, site_for_cookies, storage_access_api_status);

  if (cookieState == PrivacySetting::kStateDisallowed) {
    std::move(callback).Run();
    return;
  }

  // We have to do a quick parsing of the cookie string just to see if the
  // partitioned header is set before handing over the string to the restricted
  // cookie manager.
  net::ParsedCookie parsed_cookie(cookie);

  if (cookieState == PrivacySetting::kStateAllowed ||
      (parsed_cookie.IsValid() && parsed_cookie.IsPartitioned() &&
       parsed_cookie.IsSecure())) {
    underlying_restricted_cookie_manager_->SetCookieFromString(
        url, site_for_cookies, top_frame_origin, storage_access_api_status,
        cookie, std::move(callback));
  } else {
    std::move(callback).Run();
  }
}

void AwProxyingRestrictedCookieManager::GetCookiesString(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_frame_origin,
    net::StorageAccessApiStatus storage_access_api_status,
    bool get_version_shared_memory,
    bool is_ad_tagged,
    bool force_disable_third_party_cookies,
    GetCookiesStringCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  PrivacySetting cookieState =
      AllowCookies(url, site_for_cookies, storage_access_api_status);

  if (cookieState == PrivacySetting::kStateDisallowed) {
    std::move(callback).Run(network::mojom::kInvalidCookieVersion,
                            base::ReadOnlySharedMemoryRegion(), "");
    return;
  }

  bool disable_3pcs =
      force_disable_third_party_cookies ||
      cookieState == PrivacySetting::kPartitionedStateAllowedOnly;

  // In Android Webview the access to cookies can change dynamically. For
  // now never request a shared memory region so that a full IPC is issued
  // every time. This prevents a client retaining access to the cookie value
  // past the moment where it was denied. (crbug.com/1393050): Implement a
  // strategy so that the shared memory access can be revoked from here.
  underlying_restricted_cookie_manager_->GetCookiesString(
      url, site_for_cookies, top_frame_origin, storage_access_api_status,
      /*get_version_shared_memory=*/false, is_ad_tagged, disable_3pcs,
      std::move(callback));
}

void AwProxyingRestrictedCookieManager::CookiesEnabledFor(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_frame_origin,
    net::StorageAccessApiStatus storage_access_api_status,
    CookiesEnabledForCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  std::move(callback).Run(
      AllowCookies(url, site_for_cookies, storage_access_api_status) ==
      PrivacySetting::kStateAllowed);
}

AwProxyingRestrictedCookieManager::AwProxyingRestrictedCookieManager(
    mojo::PendingRemote<network::mojom::RestrictedCookieManager>
        underlying_restricted_cookie_manager,
    bool is_service_worker,
    const std::optional<const content::GlobalRenderFrameHostToken>&
        global_frame_token,
    AwCookieAccessPolicy* cookie_access_policy)
    : underlying_restricted_cookie_manager_(
          std::move(underlying_restricted_cookie_manager)),
      is_service_worker_(is_service_worker),
      global_frame_token_(global_frame_token),
      cookie_access_policy_(*cookie_access_policy) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}

// static
void AwProxyingRestrictedCookieManager::CreateAndBindOnIoThread(
    mojo::PendingRemote<network::mojom::RestrictedCookieManager> underlying_rcm,
    bool is_service_worker,
    const std::optional<const content::GlobalRenderFrameHostToken>&
        global_frame_token,
    mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
    AwCookieAccessPolicy* cookie_access_policy) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  auto wrapper = base::WrapUnique(new AwProxyingRestrictedCookieManager(
      std::move(underlying_rcm), is_service_worker, global_frame_token,
      cookie_access_policy));
  mojo::MakeSelfOwnedReceiver(std::move(wrapper), std::move(receiver));
}

PrivacySetting AwProxyingRestrictedCookieManager::AllowCookies(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    net::StorageAccessApiStatus storage_access_api_status) const {
  if (is_service_worker_) {
    // Service worker cookies are always first-party, so only need to check
    // the global toggle.
    return cookie_access_policy_->GetShouldAcceptCookies()
               ? PrivacySetting::kStateAllowed
               : PrivacySetting::kStateDisallowed;
  } else {
    return cookie_access_policy_->AllowCookies(
        url, site_for_cookies, global_frame_token_, storage_access_api_status);
  }
}

}  // namespace android_webview