| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // 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/metrics/user_metrics.h" |
| #include "base/strings/string_split.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/content_settings/tab_specific_content_settings.h" |
| #include "chrome/browser/permissions/permission_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.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" |
| #if defined(OS_CHROMEOS) |
| #include <utility> |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "chrome/browser/chromeos/attestation/platform_verification_dialog.h" |
| #include "chrome/browser/chromeos/settings/cros_settings.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "chromeos/settings/cros_settings_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/user_prefs/user_prefs.h" |
| #include "ui/views/widget/widget.h" |
| #elif !defined(OS_ANDROID) |
| #error This file currently only supports Chrome OS and Android. |
| #endif |
| |
| #if defined(OS_CHROMEOS) |
| using chromeos::attestation::PlatformVerificationDialog; |
| #endif |
| |
| ProtectedMediaIdentifierPermissionContext:: |
| ProtectedMediaIdentifierPermissionContext(Profile* profile) |
| : PermissionContextBase(profile, |
| CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER, |
| blink::mojom::FeaturePolicyFeature::kEncryptedMedia) |
| #if defined(OS_CHROMEOS) |
| , |
| weak_factory_(this) |
| #endif |
| { |
| } |
| |
| ProtectedMediaIdentifierPermissionContext:: |
| ~ProtectedMediaIdentifierPermissionContext() { |
| } |
| |
| #if defined(OS_CHROMEOS) |
| void ProtectedMediaIdentifierPermissionContext::DecidePermission( |
| content::WebContents* web_contents, |
| const PermissionRequestID& id, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin, |
| bool user_gesture, |
| const BrowserPermissionCallback& callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Since the dialog is modal, we only support one prompt per |web_contents|. |
| // Reject the new one if there is already one pending. See |
| // http://crbug.com/447005 |
| if (pending_requests_.count(web_contents)) { |
| callback.Run(CONTENT_SETTING_ASK); |
| return; |
| } |
| |
| // On ChromeOS, we don't use PermissionContextBase::RequestPermission() which |
| // uses the standard permission infobar/bubble UI. See http://crbug.com/454847 |
| // Instead, we show the existing platform verification UI. |
| // TODO(xhwang): Remove when http://crbug.com/454847 is fixed. |
| views::Widget* widget = PlatformVerificationDialog::ShowDialog( |
| web_contents, requesting_origin, |
| base::Bind(&ProtectedMediaIdentifierPermissionContext:: |
| OnPlatformVerificationConsentResponse, |
| weak_factory_.GetWeakPtr(), web_contents, id, |
| requesting_origin, embedding_origin, callback)); |
| |
| // This could happen when the permission is requested from an extension. See |
| // http://crbug.com/728534 |
| if (!widget) { |
| callback.Run(CONTENT_SETTING_ASK); |
| return; |
| } |
| |
| pending_requests_.insert( |
| std::make_pair(web_contents, std::make_pair(widget, id))); |
| } |
| #endif // defined(OS_CHROMEOS) |
| |
| 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()) { |
| return CONTENT_SETTING_BLOCK; |
| } |
| |
| ContentSetting content_setting = |
| PermissionContextBase::GetPermissionStatusInternal( |
| render_frame_host, requesting_origin, embedding_origin); |
| DCHECK(content_setting == CONTENT_SETTING_ALLOW || |
| content_setting == CONTENT_SETTING_BLOCK || |
| content_setting == CONTENT_SETTING_ASK); |
| |
| // For automated testing of protected content - having a prompt that |
| // requires user intervention is problematic. If the domain has been |
| // whitelisted as safe - suppress the request and allow. |
| if (content_setting == CONTENT_SETTING_ASK && |
| IsOriginWhitelisted(requesting_origin)) { |
| content_setting = CONTENT_SETTING_ALLOW; |
| } |
| |
| return content_setting; |
| } |
| |
| bool ProtectedMediaIdentifierPermissionContext::IsOriginWhitelisted( |
| const GURL& origin) { |
| const base::CommandLine& command_line = |
| *base::CommandLine::ForCurrentProcess(); |
| |
| const std::string whitelist = command_line.GetSwitchValueASCII( |
| switches::kUnsafelyAllowProtectedMediaIdentifierForDomain); |
| |
| for (const std::string& domain : base::SplitString( |
| whitelist, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { |
| if (origin.DomainIs(domain)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void ProtectedMediaIdentifierPermissionContext::UpdateTabContext( |
| const PermissionRequestID& id, |
| const GURL& requesting_frame, |
| bool allowed) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // WebContents may have gone away. |
| TabSpecificContentSettings* content_settings = |
| TabSpecificContentSettings::GetForFrame(id.render_process_id(), |
| id.render_frame_id()); |
| if (content_settings) { |
| content_settings->OnProtectedMediaIdentifierPermissionSet( |
| requesting_frame.GetOrigin(), allowed); |
| } |
| } |
| |
| bool ProtectedMediaIdentifierPermissionContext::IsRestrictedToSecureOrigins() |
| const { |
| // EME is not supported on insecure origins, see https://goo.gl/Ks5zf7 |
| // Note that origins whitelisted by --unsafely-treat-insecure-origin-as-secure |
| // flag will be treated as "secure" so they will not be affected. |
| return true; |
| } |
| |
| // TODO(xhwang): We should consolidate the "protected content" related pref |
| // across platforms. |
| bool ProtectedMediaIdentifierPermissionContext:: |
| IsProtectedMediaIdentifierEnabled() const { |
| #if defined(OS_CHROMEOS) |
| // Platform verification is not allowed in incognito or guest mode. |
| if (profile()->IsOffTheRecord() || profile()->IsGuestSession()) { |
| DVLOG(1) << "Protected media identifier disabled in incognito or guest " |
| "mode."; |
| return false; |
| } |
| |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(chromeos::switches::kSystemDevMode) && |
| !command_line->HasSwitch(chromeos::switches::kAllowRAInDevMode)) { |
| DVLOG(1) << "Protected media identifier disabled in dev mode."; |
| return false; |
| } |
| |
| // This could be disabled by the device policy or by user's master switch. |
| bool enabled_for_device = false; |
| if (!chromeos::CrosSettings::Get()->GetBoolean( |
| chromeos::kAttestationForContentProtectionEnabled, |
| &enabled_for_device) || |
| !enabled_for_device || |
| !profile()->GetPrefs()->GetBoolean(prefs::kEnableDRM)) { |
| DVLOG(1) << "Protected media identifier disabled by the user or by device " |
| "policy."; |
| return false; |
| } |
| #endif |
| |
| return true; |
| } |
| |
| #if defined(OS_CHROMEOS) |
| |
| static void ReportPermissionActionUMA(PermissionAction action) { |
| UMA_HISTOGRAM_ENUMERATION("Permissions.Action.ProtectedMedia", action, |
| PermissionAction::NUM); |
| } |
| |
| void ProtectedMediaIdentifierPermissionContext:: |
| OnPlatformVerificationConsentResponse( |
| content::WebContents* web_contents, |
| const PermissionRequestID& id, |
| const GURL& requesting_origin, |
| const GURL& embedding_origin, |
| const BrowserPermissionCallback& callback, |
| PlatformVerificationDialog::ConsentResponse response) { |
| // The request may have been canceled. Drop the callback in that case. |
| // This can happen if the tab is closed. |
| PendingRequestMap::iterator request = pending_requests_.find(web_contents); |
| if (request == pending_requests_.end()) { |
| VLOG(1) << "Platform verification ignored by user."; |
| ReportPermissionActionUMA(PermissionAction::IGNORED); |
| return; |
| } |
| |
| DCHECK(request->second.second == id); |
| pending_requests_.erase(request); |
| |
| ContentSetting content_setting = CONTENT_SETTING_ASK; |
| bool persist = false; // Whether the ContentSetting should be saved. |
| switch (response) { |
| case PlatformVerificationDialog::CONSENT_RESPONSE_NONE: |
| // This can happen if user clicked "x", or pressed "Esc", or navigated |
| // away without closing the tab. |
| VLOG(1) << "Platform verification dismissed by user."; |
| ReportPermissionActionUMA(PermissionAction::DISMISSED); |
| content_setting = CONTENT_SETTING_ASK; |
| persist = false; |
| break; |
| case PlatformVerificationDialog::CONSENT_RESPONSE_ALLOW: |
| VLOG(1) << "Platform verification accepted by user."; |
| base::RecordAction( |
| base::UserMetricsAction("PlatformVerificationAccepted")); |
| ReportPermissionActionUMA(PermissionAction::GRANTED); |
| content_setting = CONTENT_SETTING_ALLOW; |
| persist = true; |
| break; |
| case PlatformVerificationDialog::CONSENT_RESPONSE_DENY: |
| VLOG(1) << "Platform verification denied by user."; |
| base::RecordAction( |
| base::UserMetricsAction("PlatformVerificationRejected")); |
| ReportPermissionActionUMA(PermissionAction::DENIED); |
| content_setting = CONTENT_SETTING_BLOCK; |
| persist = true; |
| break; |
| } |
| |
| NotifyPermissionSet( |
| id, requesting_origin, embedding_origin, callback, |
| persist, content_setting); |
| } |
| #endif |