chromium/chrome/browser/media/protected_media_identifier_permission_context.cc

// Copyright 2013 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/media/protected_media_identifier_permission_context.h"

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_split.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/permissions/permission_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "media/base/media_switches.h"
#include "net/base/url_util.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/dbus/constants/dbus_switches.h"  // nogncheck
#endif

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include <utility>

#include "ash/constants/ash_switches.h"
#include "base/metrics/histogram_macros.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/request_type.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/user_prefs/user_prefs.h"
#endif

#if !(BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS))
#error This file currently only supports Chrome OS, Android and Windows.
#endif

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/crosapi/mojom/prefs.mojom.h"
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

ProtectedMediaIdentifierPermissionContext::
    ProtectedMediaIdentifierPermissionContext(
        content::BrowserContext* browser_context)
    : PermissionContextBase(
          browser_context,
          ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
          blink::mojom::PermissionsPolicyFeature::kEncryptedMedia) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  // base::Unretained is safe because this object is built as a singleton and
  // lives while the browser process is alive.
  attestation_enabled_observer_ = std::make_unique<CrosapiPrefObserver>(
      crosapi::mojom::PrefPath::kAttestationForContentProtectionEnabled,
      base::BindRepeating(&ProtectedMediaIdentifierPermissionContext::
                              OnAttestationEnabledChanged,
                          base::Unretained(this)));
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
}

ProtectedMediaIdentifierPermissionContext::
    ~ProtectedMediaIdentifierPermissionContext() {
}

ContentSetting
ProtectedMediaIdentifierPermissionContext::GetPermissionStatusInternal(
    content::RenderFrameHost* render_frame_host,
    const GURL& requesting_origin,
    const GURL& embedding_origin) const {
  DVLOG(1) << __func__ << ": (" << requesting_origin.spec() << ", "
           << embedding_origin.spec() << ")";

  if (!requesting_origin.is_valid() || !embedding_origin.is_valid() ||
      !IsProtectedMediaIdentifierEnabled(
          Profile::FromBrowserContext(browser_context()))) {
    return CONTENT_SETTING_BLOCK;
  }

  ContentSetting content_setting =
      permissions::PermissionContextBase::GetPermissionStatusInternal(
          render_frame_host, requesting_origin, embedding_origin);
  DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
#if BUILDFLAG(IS_ANDROID)
         content_setting == CONTENT_SETTING_ASK ||
#endif
         content_setting == CONTENT_SETTING_BLOCK);

  // For automated testing of protected content - having a prompt that
  // requires user intervention is problematic. If the domain has been
  // allowlisted as safe - suppress the request and allow.
  if (content_setting != CONTENT_SETTING_ALLOW &&
      IsOriginAllowed(requesting_origin)) {
    content_setting = CONTENT_SETTING_ALLOW;
  }

  return content_setting;
}

bool ProtectedMediaIdentifierPermissionContext::IsOriginAllowed(
    const GURL& origin) {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();

  const std::string allowlist = command_line.GetSwitchValueASCII(
      switches::kUnsafelyAllowProtectedMediaIdentifierForDomain);

  for (const std::string& domain : base::SplitString(
           allowlist, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
    if (origin.DomainIs(domain)) {
      return true;
    }
  }

  return false;
}

void ProtectedMediaIdentifierPermissionContext::UpdateTabContext(
    const permissions::PermissionRequestID& id,
    const GURL& requesting_frame,
    bool allowed) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // WebContents may have gone away.
  content_settings::PageSpecificContentSettings* content_settings =
      content_settings::PageSpecificContentSettings::GetForFrame(
          id.global_render_frame_host_id());
  if (content_settings) {
    content_settings->OnProtectedMediaIdentifierPermissionSet(
        requesting_frame.DeprecatedGetOriginAsURL(), allowed);
  }
}

// TODO(xhwang): We should consolidate the "protected content" related pref
// across platforms.
// static
bool ProtectedMediaIdentifierPermissionContext::
    IsProtectedMediaIdentifierEnabled(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
  // Identifier is not allowed in incognito or guest mode.
  if (profile != nullptr &&
      (profile->IsOffTheRecord() || profile->IsGuestSession())) {
    DVLOG(1) << "Protected media identifier disabled in incognito or guest "
                "mode.";
    return false;
  }

#if BUILDFLAG(IS_CHROMEOS)
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(chromeos::switches::kSystemDevMode) &&
      !command_line->HasSwitch(switches::kAllowRAInDevMode)) {
    DVLOG(1) << "Protected media identifier disabled in dev mode.";
    return false;
  }

  // This could be disabled by the device policy or by a switch in content
  // settings.
  bool attestation_enabled = true;
#if BUILDFLAG(IS_CHROMEOS_ASH)
  if (!ash::CrosSettings::Get()->GetBoolean(
          ash::kAttestationForContentProtectionEnabled, &attestation_enabled)) {
    attestation_enabled = false;
  }
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
  attestation_enabled = attestation_enabled_;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
  if (!attestation_enabled) {
    DVLOG(1) << "Protected media identifier disabled by the user or by device "
                "policy.";
    return false;
  }
#endif  // BUILDFLAG(IS_CHROMEOS)
#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)

  return true;
}

#if BUILDFLAG(IS_CHROMEOS_LACROS)
void ProtectedMediaIdentifierPermissionContext::OnAttestationEnabledChanged(
    base::Value value) {
  DCHECK(value.is_bool());
  attestation_enabled_ = value.GetBool();
}
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)