blob: 85320190d406bdd37175ddb5d207d124b278b648 [file] [log] [blame]
// Copyright 2021 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/webrtc/capture_policy_utils.h"
#include <vector>
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/ranges/algorithm.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
#include "chrome/browser/policy/policy_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "media/base/media_switches.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/tab_modal_confirm_dialog.h"
#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/crosapi/mojom/multi_capture_service.mojom.h"
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/multi_capture_service_ash.h"
#include "chrome/browser/ash/policy/multi_screen_capture/multi_screen_capture_policy_service.h"
#include "chrome/browser/ash/policy/multi_screen_capture/multi_screen_capture_policy_service_factory.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace {
#if BUILDFLAG(IS_CHROMEOS)
crosapi::mojom::MultiCaptureService* g_multi_capture_service_for_testing =
nullptr;
void IsMultiCaptureAllowedForAnyOriginOnMainProfileResultReceived(
base::OnceCallback<void(bool)> callback,
content::BrowserContext* context,
bool is_multi_capture_allowed_for_any_origin_on_main_profile) {
// If the new MultiScreenCaptureAllowedForUrls policy permits access, exit
// early. If not, check the legacy
// GetDisplayMediaSetSelectAllScreensAllowedForUrls policy.
if (is_multi_capture_allowed_for_any_origin_on_main_profile) {
std::move(callback).Run(true);
return;
}
// TODO(b/329064666): Remove the checks below once the pivot to IWAs is
// complete.
Profile* profile = Profile::FromBrowserContext(context);
if (!profile) {
std::move(callback).Run(false);
return;
}
HostContentSettingsMap* host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile);
if (!host_content_settings_map) {
std::move(callback).Run(false);
return;
}
ContentSettingsForOneType content_settings =
host_content_settings_map->GetSettingsForOneType(
ContentSettingsType::ALL_SCREEN_CAPTURE);
std::move(callback).Run(base::ranges::any_of(
content_settings, [](const ContentSettingPatternSource& source) {
return source.GetContentSetting() ==
ContentSetting::CONTENT_SETTING_ALLOW;
}));
}
void CheckAllScreensMediaAllowedForIwaResultReceived(
base::OnceCallback<void(bool)> callback,
const GURL& url,
content::BrowserContext* context,
bool result) {
if (result) {
std::move(callback).Run(true);
return;
}
Profile* profile = Profile::FromBrowserContext(context);
if (!profile) {
std::move(callback).Run(false);
return;
}
HostContentSettingsMap* host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile);
if (!host_content_settings_map) {
std::move(callback).Run(false);
return;
}
ContentSetting auto_accept_enabled =
host_content_settings_map->GetContentSetting(
url, url, ContentSettingsType::ALL_SCREEN_CAPTURE);
std::move(callback).Run(auto_accept_enabled ==
ContentSetting::CONTENT_SETTING_ALLOW);
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
namespace capture_policy {
// This pref connects to the GetDisplayMediaSetSelectAllScreensAllowedForUrls
// policy. To avoid dynamic refresh, this pref will not be read directly, but
// the value will be copied manually to the
// kManagedAccessToGetAllScreensMediaInSessionAllowedForUrls pref, which is then
// consumed by content settings to check if access to `getAllScreensMedia` shall
// be permitted for a given origin.
// TODO(b/329064666): Remove this pref once the pivot to IWAs is complete.
const char kManagedAccessToGetAllScreensMediaAllowedForUrls[] =
"profile.managed_access_to_get_all_screens_media_allowed_for_urls";
#if BUILDFLAG(IS_CHROMEOS_ASH)
// This pref connects to the MultiScreenCaptureAllowedForUrls policy and will
// replace the deprecated GetDisplayMediaSetSelectAllScreensAllowedForUrls
// policy once the pivot to IWAs is complete.
const char kManagedMultiScreenCaptureAllowedForUrls[] =
"profile.managed_multi_screen_capture_allowed_for_urls";
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace {
struct RestrictedCapturePolicy {
const char* pref_name;
AllowedScreenCaptureLevel capture_level;
};
} // namespace
#if BUILDFLAG(IS_CHROMEOS)
void SetMultiCaptureServiceForTesting(
crosapi::mojom::MultiCaptureService* service) {
CHECK_IS_TEST();
CHECK(!service || !g_multi_capture_service_for_testing);
g_multi_capture_service_for_testing = service;
}
crosapi::mojom::MultiCaptureService* GetMultiCaptureService() {
if (g_multi_capture_service_for_testing) {
return g_multi_capture_service_for_testing;
}
return crosapi::CrosapiManager::Get()
->crosapi_ash()
->multi_capture_service_ash();
}
#endif // BUILDFLAG(IS_CHROMEOS)
bool IsOriginInList(const GURL& request_origin,
const base::Value::List& allowed_origins) {
// Though we are not technically a Content Setting, ContentSettingsPattern
// aligns better than URLMatcher with the rules from:
// https://chromeenterprise.google/policies/url-patterns/.
for (const auto& value : allowed_origins) {
if (!value.is_string()) {
continue;
}
ContentSettingsPattern pattern =
ContentSettingsPattern::FromString(value.GetString());
if (pattern.IsValid() && pattern.Matches(request_origin)) {
return true;
}
}
return false;
}
AllowedScreenCaptureLevel GetAllowedCaptureLevel(
const GURL& request_origin,
content::WebContents* capturer_web_contents) {
// Since the UI for capture doesn't clip against picture in picture windows
// properly on all platforms, and since it's not clear that we actually want
// to support this anyway, turn it off for now. Note that direct calls into
// `GetAllowedCaptureLevel(..., PrefService)` will miss this check.
if (!base::FeatureList::IsEnabled(media::kDocumentPictureInPictureCapture) &&
PictureInPictureWindowManager::IsChildWebContents(
capturer_web_contents)) {
return AllowedScreenCaptureLevel::kDisallowed;
}
// If we can't get the PrefService, then we won't apply any restrictions.
Profile* profile =
Profile::FromBrowserContext(capturer_web_contents->GetBrowserContext());
if (!profile) {
return AllowedScreenCaptureLevel::kUnrestricted;
}
const PrefService* prefs = profile->GetPrefs();
if (!prefs) {
return AllowedScreenCaptureLevel::kUnrestricted;
}
return GetAllowedCaptureLevel(request_origin, *prefs);
}
AllowedScreenCaptureLevel GetAllowedCaptureLevel(const GURL& request_origin,
const PrefService& prefs) {
// Walk through the different "levels" of restriction in priority order. If
// an origin is in a more restrictive list, it is more restricted.
// Note that we only store the pref name and not the pref value here, as we
// want to look the pref value up each time (thus meaning we could not make
// this a static), as the value can change.
static constexpr std::array<RestrictedCapturePolicy, 4>
kScreenCapturePolicyLists{{{prefs::kSameOriginTabCaptureAllowedByOrigins,
AllowedScreenCaptureLevel::kSameOrigin},
{prefs::kTabCaptureAllowedByOrigins,
AllowedScreenCaptureLevel::kTab},
{prefs::kWindowCaptureAllowedByOrigins,
AllowedScreenCaptureLevel::kWindow},
{prefs::kScreenCaptureAllowedByOrigins,
AllowedScreenCaptureLevel::kDesktop}}};
for (const auto& policy_list : kScreenCapturePolicyLists) {
if (IsOriginInList(request_origin, prefs.GetList(policy_list.pref_name))) {
return policy_list.capture_level;
}
}
// If we've reached this point our origin wasn't in any of the override lists.
// That means that either everything is allowed or nothing is allowed, based
// on what |kScreenCaptureAllowed| is set to.
if (prefs.GetBoolean(prefs::kScreenCaptureAllowed)) {
return AllowedScreenCaptureLevel::kUnrestricted;
}
return AllowedScreenCaptureLevel::kDisallowed;
}
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterListPref(kManagedAccessToGetAllScreensMediaAllowedForUrls);
#if BUILDFLAG(IS_CHROMEOS_ASH)
registry->RegisterListPref(kManagedMultiScreenCaptureAllowedForUrls);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
void CheckGetAllScreensMediaAllowedForAnyOrigin(
content::BrowserContext* context,
base::OnceCallback<void(bool)> callback) {
#if BUILDFLAG(IS_CHROMEOS)
if (crosapi::mojom::MultiCaptureService* multi_capture_service =
GetMultiCaptureService()) {
multi_capture_service->IsMultiCaptureAllowedForAnyOriginOnMainProfile(
base::BindOnce(
IsMultiCaptureAllowedForAnyOriginOnMainProfileResultReceived,
std::move(callback), context));
} else {
// If the multi capture service is not available with the required version,
// fall back to the original flow using the deprecated policy.
IsMultiCaptureAllowedForAnyOriginOnMainProfileResultReceived(
std::move(callback), context, /*result=*/false);
}
#else
std::move(callback).Run(false);
#endif // BUILDFLAG(IS_CHROMEOS)
}
void CheckGetAllScreensMediaAllowed(content::BrowserContext* context,
const GURL& url,
base::OnceCallback<void(bool)> callback) {
#if BUILDFLAG(IS_CHROMEOS)
crosapi::mojom::MultiCaptureService* multi_capture_service =
GetMultiCaptureService();
if (multi_capture_service) {
multi_capture_service->IsMultiCaptureAllowed(
url, base::BindOnce(&CheckAllScreensMediaAllowedForIwaResultReceived,
std::move(callback), std::move(url), context));
} else {
// If the multi capture service is not available with the required version,
// fall back to the original flow using the deprecated policy.
CheckAllScreensMediaAllowedForIwaResultReceived(
std::move(callback), std::move(url), context, /*result=*/false);
}
#else
std::move(callback).Run(false);
#endif // BUILDFLAG(IS_CHROMEOS)
}
#if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
bool IsTransientActivationRequiredForGetDisplayMedia(
content::WebContents* contents) {
if (!base::FeatureList::IsEnabled(
blink::features::kGetDisplayMediaRequiresUserActivation)) {
return false;
}
if (!contents) {
return true;
}
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
if (!profile) {
return true;
}
PrefService* prefs = profile->GetPrefs();
if (!prefs) {
return true;
}
return !policy::IsOriginInAllowlist(
contents->GetURL(), prefs,
prefs::kScreenCaptureWithoutGestureAllowedForOrigins);
}
#endif // BUILDFLAG(ENABLE_SCREEN_CAPTURE)
DesktopMediaList::WebContentsFilter GetIncludableWebContentsFilter(
const GURL& request_origin,
AllowedScreenCaptureLevel capture_level) {
switch (capture_level) {
case AllowedScreenCaptureLevel::kDisallowed:
return base::BindRepeating(
[](content::WebContents* wc) { return false; });
case AllowedScreenCaptureLevel::kSameOrigin:
return base::BindRepeating(
[](const GURL& request_origin, content::WebContents* web_contents) {
DCHECK(web_contents);
return !PictureInPictureWindowManager::IsChildWebContents(
web_contents) &&
url::IsSameOriginWith(request_origin,
web_contents->GetLastCommittedURL()
.DeprecatedGetOriginAsURL());
},
request_origin);
default:
return base::BindRepeating([](content::WebContents* web_contents) {
DCHECK(web_contents);
return !PictureInPictureWindowManager::IsChildWebContents(web_contents);
});
}
}
void FilterMediaList(std::vector<DesktopMediaList::Type>& media_types,
AllowedScreenCaptureLevel capture_level) {
std::erase_if(
media_types, [capture_level](const DesktopMediaList::Type& type) {
switch (type) {
case DesktopMediaList::Type::kNone:
NOTREACHED();
// SameOrigin is more restrictive than just Tabs, so as long as
// at least SameOrigin is allowed, these entries should stay.
// They should be filtered later by the caller.
case DesktopMediaList::Type::kCurrentTab:
case DesktopMediaList::Type::kWebContents:
return capture_level < AllowedScreenCaptureLevel::kSameOrigin;
case DesktopMediaList::Type::kWindow:
return capture_level < AllowedScreenCaptureLevel::kWindow;
case DesktopMediaList::Type::kScreen:
return capture_level < AllowedScreenCaptureLevel::kDesktop;
}
});
}
#if !BUILDFLAG(IS_ANDROID)
class CaptureTerminatedDialogDelegate : public TabModalConfirmDialogDelegate {
public:
explicit CaptureTerminatedDialogDelegate(content::WebContents* web_contents)
: TabModalConfirmDialogDelegate(web_contents) {}
~CaptureTerminatedDialogDelegate() override = default;
std::u16string GetTitle() override {
return l10n_util::GetStringUTF16(
IDS_TAB_CAPTURE_TERMINATED_BY_POLICY_TITLE);
}
std::u16string GetDialogMessage() override {
return l10n_util::GetStringUTF16(IDS_TAB_CAPTURE_TERMINATED_BY_POLICY_TEXT);
}
int GetDialogButtons() const override {
return static_cast<int>(ui::mojom::DialogButton::kOk);
}
};
#endif
void ShowCaptureTerminatedDialog(content::WebContents* contents) {
#if !BUILDFLAG(IS_ANDROID)
TabModalConfirmDialog::Create(
std::make_unique<CaptureTerminatedDialogDelegate>(contents), contents);
#endif
}
} // namespace capture_policy