blob: 6ca55e8a86cd4b04164d30cfbfc2e3c89d039ff9 [file] [log] [blame]
// Copyright 2021 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/webrtc/capture_policy_utils.h"
#include "base/containers/cxx20_erase_vector.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
#include "url/origin.h"
#if !defined(OS_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
namespace capture_policy {
namespace {
struct RestrictedCapturePolicy {
const char* pref_name;
AllowedScreenCaptureLevel capture_level;
};
} // namespace
bool IsOriginInList(const GURL& request_origin,
const base::Value* allowed_origins) {
if (!allowed_origins || !allowed_origins->is_list())
return false;
// 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->GetList()) {
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) {
// 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;
}
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 url::IsSameOriginWith(
request_origin,
web_contents->GetLastCommittedURL().DeprecatedGetOriginAsURL());
},
request_origin);
default:
return base::BindRepeating([](content::WebContents* wc) { return true; });
}
}
void FilterMediaList(std::vector<DesktopMediaList::Type>& media_types,
AllowedScreenCaptureLevel capture_level) {
base::EraseIf(
media_types, [capture_level](const DesktopMediaList::Type& type) {
switch (type) {
case DesktopMediaList::Type::kNone:
NOTREACHED();
return false;
// 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 !defined(OS_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 ui::DIALOG_BUTTON_OK; }
};
#endif
void ShowCaptureTerminatedDialog(content::WebContents* contents) {
#if !defined(OS_ANDROID)
TabModalConfirmDialog::Create(
std::make_unique<CaptureTerminatedDialogDelegate>(contents), contents);
#endif
}
} // namespace capture_policy