// 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/chromeos/policy/dlp/dlp_content_manager.h"
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/policy/dlp/dialogs/dlp_warn_notifier.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager_observer.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_notification_helper.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
#include "chrome/browser/enterprise/data_controls/dlp_reporting_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/enterprise/data_controls/core/browser/dlp_histogram_helper.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
namespace policy {
namespace {
// Delay to wait to resume a screen share after a change in the confidentiality
// of captured data, to prevent flickering between resumed and paused states
// while the new content is being loaded. See b/259181514.
base::TimeDelta kScreenShareResumeDelay = base::Milliseconds(500);
// Reports events to `reporting_manager`.
void ReportEvent(GURL url,
DlpRulesManager::Restriction restriction,
DlpRulesManager::Level level,
data_controls::DlpReportingManager* reporting_manager) {
DCHECK(reporting_manager);
DlpRulesManager* rules_manager =
DlpRulesManagerFactory::GetForPrimaryProfile();
if (!rules_manager)
return;
DlpRulesManager::RuleMetadata rule_metadata;
const std::string src_pattern = rules_manager->GetSourceUrlPattern(
url, restriction, level, &rule_metadata);
const std::string src_url = url.is_empty() ? src_pattern : url.spec();
reporting_manager->ReportEvent(src_url, restriction, level,
rule_metadata.name,
rule_metadata.obfuscated_id);
}
// Helper method to check whether the restriction level is kBlock.
bool IsBlocked(RestrictionLevelAndUrl restriction_info) {
return restriction_info.level == DlpRulesManager::Level::kBlock;
}
// Helper method to check whether the restriction level is kWarn.
bool IsWarn(RestrictionLevelAndUrl restriction_info) {
return restriction_info.level == DlpRulesManager::Level::kWarn;
}
// Helper method to check if event should be reported.
// Does not apply to warning mode reporting.
bool IsReported(RestrictionLevelAndUrl restriction_info) {
return restriction_info.level == DlpRulesManager::Level::kReport ||
IsBlocked(restriction_info);
}
// Maps restriction to the correct suffix used for logging WarnProceeded
// metrics. Returns the suffix for supported restrictions and null otherwise.
const std::optional<std::string> RestrictionToWarnProceededUMASuffix(
DlpRulesManager::Restriction restriction) {
switch (restriction) {
case DlpRulesManager::Restriction::kScreenShare:
return std::make_optional(
data_controls::dlp::kScreenShareWarnProceededUMA);
case DlpRulesManager::Restriction::kPrinting:
return std::make_optional(data_controls::dlp::kPrintingWarnProceededUMA);
case DlpRulesManager::Restriction::kScreenshot:
return std::make_optional(
data_controls::dlp::kScreenshotWarnProceededUMA);
case DlpRulesManager::Restriction::kUnknownRestriction:
case DlpRulesManager::Restriction::kClipboard:
case DlpRulesManager::Restriction::kPrivacyScreen:
case DlpRulesManager::Restriction::kFiles:
NOTREACHED_IN_MIGRATION();
return std::nullopt;
}
}
} // namespace
DlpContentManager::WebContentsInfo::WebContentsInfo() = default;
DlpContentManager::WebContentsInfo::WebContentsInfo(
content::WebContents* web_contents,
DlpContentRestrictionSet restriction_set,
std::vector<DlpContentTabHelper::RfhInfo> rfh_info_vector)
: web_contents(web_contents),
restriction_set(std::move(restriction_set)),
rfh_info_vector(std::move(rfh_info_vector)) {}
DlpContentManager::WebContentsInfo::WebContentsInfo(const WebContentsInfo&) =
default;
DlpContentManager::WebContentsInfo&
DlpContentManager::WebContentsInfo::operator=(const WebContentsInfo&) = default;
DlpContentManager::WebContentsInfo::~WebContentsInfo() = default;
// static
DlpContentManager* DlpContentManager::Get() {
return static_cast<DlpContentManager*>(DlpContentObserver::Get());
}
DlpContentRestrictionSet DlpContentManager::GetConfidentialRestrictions(
content::WebContents* web_contents) const {
if (!base::Contains(confidential_web_contents_, web_contents))
return DlpContentRestrictionSet();
return confidential_web_contents_.at(web_contents);
}
bool DlpContentManager::IsScreenShareBlocked(
content::WebContents* web_contents) const {
return IsBlocked(GetConfidentialRestrictions(web_contents)
.GetRestrictionLevelAndUrl(
policy::DlpContentRestriction::kScreenShare));
}
void DlpContentManager::CheckPrintingRestriction(
content::WebContents* web_contents,
content::GlobalRenderFrameHostId rfh_id,
WarningCallback callback) {
const RestrictionLevelAndUrl restriction_info =
GetPrintingRestrictionInfo(web_contents, rfh_id);
MaybeReportEvent(restriction_info, DlpRulesManager::Restriction::kPrinting);
data_controls::DlpBooleanHistogram(data_controls::dlp::kPrintingBlockedUMA,
IsBlocked(restriction_info));
data_controls::DlpBooleanHistogram(data_controls::dlp::kPrintingWarnedUMA,
IsWarn(restriction_info));
if (IsBlocked(restriction_info)) {
ShowDlpPrintDisabledNotification();
std::move(callback).Run(false);
return;
}
if (IsWarn(restriction_info)) {
// Check if the contents were already allowed and don't warn in that case.
if (user_allowed_contents_cache_.Contains(
web_contents, DlpRulesManager::Restriction::kPrinting)) {
ReportWarningProceededEvent(restriction_info.url,
DlpRulesManager::Restriction::kPrinting,
reporting_manager_);
data_controls::DlpBooleanHistogram(
data_controls::dlp::kPrintingWarnSilentProceededUMA, true);
std::move(callback).Run(true);
return;
}
// Immediately report a warning event.
ReportWarningEvent(restriction_info.url,
DlpRulesManager::Restriction::kPrinting);
// Report a warning proceeded event only after a user proceeds with printing
// in the warn dialog.
auto reporting_callback = base::BindOnce(
&MaybeReportWarningProceededEvent, restriction_info.url,
DlpRulesManager::Restriction::kPrinting, reporting_manager_);
warn_notifier_->ShowDlpPrintWarningDialog(base::BindOnce(
&DlpContentManager::OnDlpWarnDialogReply, base::Unretained(this),
DlpConfidentialContents({web_contents}),
DlpRulesManager::Restriction::kPrinting,
std::move(reporting_callback).Then(std::move(callback))));
return;
}
// No restrictions apply.
std::move(callback).Run(true);
}
bool DlpContentManager::IsScreenshotApiRestricted(
content::WebContents* web_contents) {
const RestrictionLevelAndUrl restriction_info =
GetConfidentialRestrictions(web_contents)
.GetRestrictionLevelAndUrl(DlpContentRestriction::kScreenshot);
MaybeReportEvent(restriction_info, DlpRulesManager::Restriction::kScreenshot);
if (IsWarn(restriction_info))
ReportWarningEvent(restriction_info.url,
DlpRulesManager::Restriction::kScreenshot);
data_controls::DlpBooleanHistogram(data_controls::dlp::kScreenshotBlockedUMA,
IsBlocked(restriction_info));
data_controls::DlpBooleanHistogram(data_controls::dlp::kScreenshotWarnedUMA,
IsWarn(restriction_info));
// TODO(crbug.com/1252736): Properly handle WARN for screenshots API.
return IsBlocked(restriction_info) || IsWarn(restriction_info);
}
void DlpContentManager::SetReportingManagerForTesting(
data_controls::DlpReportingManager* reporting_manager) {
DCHECK(!reporting_manager_);
DCHECK(reporting_manager);
reporting_manager_ = reporting_manager;
}
void DlpContentManager::SetWarnNotifierForTesting(
std::unique_ptr<DlpWarnNotifier> warn_notifier) {
DCHECK(warn_notifier);
warn_notifier_ = std::move(warn_notifier);
}
void DlpContentManager::ResetWarnNotifierForTesting() {
warn_notifier_ = std::make_unique<DlpWarnNotifier>();
}
// static
void DlpContentManager::SetScreenShareResumeDelayForTesting(
base::TimeDelta delay) {
kScreenShareResumeDelay = delay;
}
DlpContentManager::ScreenShareInfo::ScreenShareInfo(
const std::string& label,
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
base::OnceClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback)
: label_(label),
media_id_(media_id),
application_title_(application_title),
stop_callback_(std::move(stop_callback)),
state_change_callback_(std::move(state_change_callback)),
source_callback_(std::move(source_callback)) {
auto* web_contents = GetWebContentsFromMediaId(media_id);
web_contents_ = web_contents ? web_contents->GetWeakPtr() : nullptr;
}
DlpContentManager::ScreenShareInfo::~ScreenShareInfo() {
// Hide notifications if necessary.
HideNotifications();
}
void DlpContentManager::ScreenShareInfo::UpdateAfterSourceChange(
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
base::OnceClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback) {
DCHECK(state_ == State::kRunningBeforeSourceChange ||
state_ == State::kPausedBeforeSourceChange);
DCHECK(new_media_id_ == media_id);
DCHECK(media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS);
media_id_ = media_id;
stop_callback_ = std::move(stop_callback);
state_change_callback_ = std::move(state_change_callback);
source_callback_ = std::move(source_callback);
auto* web_contents = GetWebContentsFromMediaId(media_id);
web_contents_ = web_contents ? web_contents->GetWeakPtr() : nullptr;
// If it's a resume after source change, request to start it if pending.
StartIfPending();
// This is called from AddScreenShare() which is only called when a new stream
// is starting or when the source was successfully changed and the stream is
// running again, so we can set the state to running.
if (state_ == State::kPausedBeforeSourceChange) {
state_ = State::kRunning;
MaybeUpdateNotifications();
} else {
state_ = State::kRunning;
}
}
bool DlpContentManager::ScreenShareInfo::operator==(
const DlpContentManager::ScreenShareInfo& other) const {
return label_ == other.label_ && media_id_ == other.media_id_;
}
bool DlpContentManager::ScreenShareInfo::operator!=(
const DlpContentManager::ScreenShareInfo& other) const {
return !(*this == other);
}
const content::DesktopMediaID& DlpContentManager::ScreenShareInfo::media_id()
const {
return media_id_;
}
const content::DesktopMediaID&
DlpContentManager::ScreenShareInfo::new_media_id() const {
return new_media_id_;
}
void DlpContentManager::ScreenShareInfo::set_new_media_id(
const content::DesktopMediaID& media_id) {
new_media_id_ = media_id;
}
const std::string& DlpContentManager::ScreenShareInfo::label() const {
return label_;
}
const std::u16string& DlpContentManager::ScreenShareInfo::application_title()
const {
return application_title_;
}
DlpContentManager::ScreenShareInfo::State
DlpContentManager::ScreenShareInfo::state() const {
return state_;
}
base::WeakPtr<content::WebContents>
DlpContentManager::ScreenShareInfo::web_contents() const {
return web_contents_;
}
void DlpContentManager::ScreenShareInfo::set_dialog_widget(
base::WeakPtr<views::Widget> dialog_widget) {
DCHECK(!HasOpenDialogWidget());
dialog_widget_ = dialog_widget;
}
void DlpContentManager::ScreenShareInfo::set_latest_confidential_contents_info(
ConfidentialContentsInfo confidential_contents_info) {
latest_confidential_contents_info_ = confidential_contents_info;
}
void DlpContentManager::ScreenShareInfo::StartIfPending() {
if (pending_start_on_source_change_) {
state_change_callback_.Run(media_id_,
blink::mojom::MediaStreamStateChange::PLAY);
pending_start_on_source_change_ = false;
}
}
const RestrictionLevelAndUrl&
DlpContentManager::ScreenShareInfo::GetLatestRestriction() const {
return latest_confidential_contents_info_.restriction_info;
}
const DlpConfidentialContents&
DlpContentManager::ScreenShareInfo::GetConfidentialContents() const {
return latest_confidential_contents_info_.confidential_contents;
}
void DlpContentManager::ScreenShareInfo::Pause() {
DCHECK_EQ(state_, State::kRunning);
state_change_callback_.Run(media_id_,
blink::mojom::MediaStreamStateChange::PAUSE);
state_ = State::kPaused;
}
void DlpContentManager::ScreenShareInfo::Resume() {
DCHECK_EQ(state_, State::kPaused);
// In case of a tab share try to update the source to the current WebContents
// frame id in case it was navigated to a different page with another frame.
// Switching to a new source will resume the share so we don't need to do it
// here explicitly.
if (media_id_.type == content::DesktopMediaID::TYPE_WEB_CONTENTS &&
web_contents_ && source_callback_) {
content::RenderFrameHost* main_frame = web_contents_->GetPrimaryMainFrame();
DCHECK(main_frame);
source_callback_.Run(
content::DesktopMediaID(
content::DesktopMediaID::TYPE_WEB_CONTENTS,
content::DesktopMediaID::kNullId,
content::WebContentsMediaCaptureId(
main_frame->GetProcess()->GetID(), main_frame->GetRoutingID())),
captured_surface_control_active_);
// Start after source will be changed and notified.
pending_start_on_source_change_ = true;
} else {
state_change_callback_.Run(media_id_,
blink::mojom::MediaStreamStateChange::PLAY);
}
state_ = State::kRunning;
}
void DlpContentManager::ScreenShareInfo::ChangeStateBeforeSourceChange() {
DCHECK(state_ == State::kPaused || state_ == State::kRunning);
DCHECK(media_id_.type == content::DesktopMediaID::TYPE_WEB_CONTENTS);
if (state_ == State::kPaused) {
state_ = ScreenShareInfo::State::kPausedBeforeSourceChange;
} else if (state_ == State::kRunning) {
state_ = State::kRunningBeforeSourceChange;
} else {
// This should only be called if state_ is Running or Paused.
NOTREACHED_IN_MIGRATION();
}
}
void DlpContentManager::ScreenShareInfo::Stop() {
DCHECK_NE(state_, State::kStopped);
if (stop_callback_) {
std::move(stop_callback_).Run();
state_ = State::kStopped;
} else {
NOTREACHED_IN_MIGRATION();
}
}
void DlpContentManager::ScreenShareInfo::MaybeUpdateNotifications() {
UpdatePausedNotification(/*show=*/state_ == State::kPaused);
UpdateResumedNotification(/*show=*/state_ == State::kRunning);
}
void DlpContentManager::ScreenShareInfo::HideNotifications() {
UpdatePausedNotification(/*show=*/false);
UpdateResumedNotification(/*show=*/false);
}
void DlpContentManager::ScreenShareInfo::MaybeCloseDialogWidget() {
if (dialog_widget_) {
dialog_widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
}
bool DlpContentManager::ScreenShareInfo::HasOpenDialogWidget() {
return dialog_widget_ && !dialog_widget_->IsClosed();
}
void DlpContentManager::ScreenShareInfo::SetCapturedSurfaceControlActive() {
captured_surface_control_active_ = true;
}
base::WeakPtr<DlpContentManager::ScreenShareInfo>
DlpContentManager::ScreenShareInfo::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void DlpContentManager::ScreenShareInfo::UpdatePausedNotification(bool show) {
if ((notification_state_ == NotificationState::kShowingPausedNotification) ==
show)
return;
if (show) {
DCHECK_EQ(state_, State::kPaused);
ShowDlpScreenSharePausedNotification(label_, application_title_);
notification_state_ = NotificationState::kShowingPausedNotification;
} else {
HideDlpScreenSharePausedNotification(label_);
notification_state_ = NotificationState::kNotShowingNotification;
}
}
void DlpContentManager::ScreenShareInfo::UpdateResumedNotification(bool show) {
if ((notification_state_ == NotificationState::kShowingResumedNotification) ==
show)
return;
if (show) {
DCHECK_EQ(state_, State::kRunning);
ShowDlpScreenShareResumedNotification(label_, application_title_);
notification_state_ = NotificationState::kShowingResumedNotification;
} else {
HideDlpScreenShareResumedNotification(label_);
notification_state_ = NotificationState::kNotShowingNotification;
}
}
void DlpContentManager::AddObserver(DlpContentManagerObserver* observer,
DlpContentRestriction restriction) {
observer_lists_[static_cast<int>(restriction)].AddObserver(observer);
}
void DlpContentManager::OnScreenShareSourceChanging(
const std::string& label,
const content::DesktopMediaID& old_media_id,
const content::DesktopMediaID& new_media_id,
bool captured_surface_control_active) {
for (auto& screen_share : running_screen_shares_) {
if (screen_share->label() == label &&
screen_share->media_id() == old_media_id) {
if (captured_surface_control_active) {
screen_share->SetCapturedSurfaceControlActive();
}
if (screen_share->new_media_id() != new_media_id) {
screen_share->ChangeStateBeforeSourceChange();
screen_share->set_new_media_id(new_media_id);
}
}
}
}
void DlpContentManager::RemoveObserver(
const DlpContentManagerObserver* observer,
DlpContentRestriction restriction) {
observer_lists_[static_cast<int>(restriction)].RemoveObserver(observer);
}
std::vector<DlpContentManager::WebContentsInfo>
DlpContentManager::GetWebContentsInfo() const {
std::vector<WebContentsInfo> web_contents_info_vector;
for (const auto& [web_contents, restriction_set] :
confidential_web_contents_) {
DlpContentManager::WebContentsInfo web_contents_info(web_contents,
restriction_set, {});
auto* tab_helper = DlpContentTabHelper::FromWebContents(web_contents);
if (tab_helper) {
web_contents_info.rfh_info_vector = tab_helper->GetFramesInfo();
}
web_contents_info_vector.push_back(std::move(web_contents_info));
}
return web_contents_info_vector;
}
DlpContentManager::DlpContentManager() {
// Start observing tab strip models for all browsers.
BrowserList* browser_list = BrowserList::GetInstance();
for (Browser* browser : *browser_list)
browser->tab_strip_model()->AddObserver(this);
browser_list->AddObserver(this);
}
DlpContentManager::~DlpContentManager() {
BrowserList* browser_list = BrowserList::GetInstance();
browser_list->RemoveObserver(this);
for (Browser* browser : *browser_list)
browser->tab_strip_model()->RemoveObserver(this);
}
// static
void DlpContentManager::ReportWarningProceededEvent(
const GURL& url,
DlpRulesManager::Restriction restriction,
data_controls::DlpReportingManager* reporting_manager) {
if (!reporting_manager)
return;
DlpRulesManager* rules_manager =
DlpRulesManagerFactory::GetForPrimaryProfile();
if (rules_manager) {
DlpRulesManager::RuleMetadata rule_metadata;
const std::string src_pattern = rules_manager->GetSourceUrlPattern(
url, restriction, DlpRulesManager::Level::kWarn, &rule_metadata);
const std::string src_url = url.is_empty() ? src_pattern : url.spec();
reporting_manager->ReportWarningProceededEvent(
src_url, restriction, rule_metadata.name, rule_metadata.obfuscated_id);
}
}
// static
bool DlpContentManager::MaybeReportWarningProceededEvent(
GURL url,
DlpRulesManager::Restriction restriction,
data_controls::DlpReportingManager* reporting_manager,
bool should_proceed) {
if (should_proceed) {
ReportWarningProceededEvent(url, restriction, reporting_manager);
}
return should_proceed;
}
// static
content::WebContents* DlpContentManager::GetWebContentsFromMediaId(
const content::DesktopMediaID& media_id) {
if (media_id.type != content::DesktopMediaID::Type::TYPE_WEB_CONTENTS)
return nullptr;
return content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(
media_id.web_contents_id.render_process_id,
media_id.web_contents_id.main_render_frame_id));
}
void DlpContentManager::Init() {
DlpRulesManager* rules_manager =
DlpRulesManagerFactory::GetForPrimaryProfile();
if (rules_manager)
reporting_manager_ =
DlpRulesManagerFactory::GetForPrimaryProfile()->GetReportingManager();
warn_notifier_ = std::make_unique<DlpWarnNotifier>();
}
void DlpContentManager::OnConfidentialityChanged(
content::WebContents* web_contents,
const DlpContentRestrictionSet& restriction_set) {
DlpContentRestrictionSet old_restriction_set;
if (confidential_web_contents_.contains(web_contents)) {
old_restriction_set = confidential_web_contents_[web_contents];
}
UpdateConfidentiality(web_contents, restriction_set);
NotifyOnConfidentialityChanged(old_restriction_set, restriction_set,
web_contents);
}
void DlpContentManager::OnWebContentsDestroyed(
content::WebContents* web_contents) {
RemoveFromConfidential(web_contents);
}
void DlpContentManager::OnBrowserAdded(Browser* browser) {
browser->tab_strip_model()->AddObserver(this);
}
void DlpContentManager::OnBrowserRemoved(Browser* browser) {
browser->tab_strip_model()->RemoveObserver(this);
}
void DlpContentManager::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
// Checking only after selecting the possible moved tab as in this case it
// already was added to the new window.
if (change.type() == TabStripModelChange::kSelectionOnly) {
TabLocationMaybeChanged(selection.new_contents);
}
}
void DlpContentManager::RemoveFromConfidential(
content::WebContents* web_contents) {
confidential_web_contents_.erase(web_contents);
}
RestrictionLevelAndUrl DlpContentManager::GetPrintingRestrictionInfo(
content::WebContents* web_contents,
content::GlobalRenderFrameHostId rfh_id) const {
// If we're viewing the PDF in a MimeHandlerViewGuest, use its embedded
// WebContents.
auto* guest_view =
extensions::MimeHandlerViewGuest::FromRenderFrameHostId(rfh_id);
web_contents =
guest_view ? guest_view->embedder_web_contents() : web_contents;
return GetConfidentialRestrictions(web_contents)
.GetRestrictionLevelAndUrl(DlpContentRestriction::kPrint);
}
DlpContentManager::ConfidentialContentsInfo
DlpContentManager::GetScreenShareConfidentialContentsInfoForWebContents(
content::WebContents* web_contents) const {
ConfidentialContentsInfo info;
if (web_contents && !web_contents->IsBeingDestroyed()) {
info.restriction_info =
GetConfidentialRestrictions(web_contents)
.GetRestrictionLevelAndUrl(DlpContentRestriction::kScreenShare);
if (info.restriction_info.level != DlpRulesManager::Level::kNotSet)
info.confidential_contents.Add(web_contents);
}
return info;
}
void DlpContentManager::ProcessScreenShareRestriction(
const std::u16string& application_title,
ConfidentialContentsInfo info,
WarningCallback callback) {
data_controls::DlpBooleanHistogram(data_controls::dlp::kScreenShareBlockedUMA,
IsBlocked(info.restriction_info));
data_controls::DlpBooleanHistogram(data_controls::dlp::kScreenShareWarnedUMA,
IsWarn(info.restriction_info));
if (IsBlocked(info.restriction_info)) {
MaybeReportEvent(info.restriction_info,
DlpRulesManager::Restriction::kScreenShare);
ShowDlpScreenShareDisabledNotification(application_title);
std::move(callback).Run(false);
return;
}
if (IsWarn(info.restriction_info)) {
// Check which of the contents were already allowed and don't warn for
// those.
RemoveAllowedContents(info.confidential_contents,
DlpRulesManager::Restriction::kScreenShare);
if (info.confidential_contents.IsEmpty()) {
// The user already allowed all the visible content.
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenShareWarnSilentProceededUMA, true);
std::move(callback).Run(true);
return;
}
ReportWarningEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenShare);
// base::Unretained(this) is safe here because DlpContentManager is
// initialized as a singleton that's always available in the system.
//
// Don't report warning proceeded events here. They are reported in
// DlpContentManager::CheckRunningScreenShares(), which is called when
// screen share starts by DlpContentManager::OnScreenShareStarted().
warn_notifier_->ShowDlpScreenShareWarningDialog(
base::BindOnce(&DlpContentManager::OnDlpWarnDialogReply,
base::Unretained(this), info.confidential_contents,
DlpRulesManager::Restriction::kScreenShare,
std::move(callback)),
info.confidential_contents, application_title);
return;
}
// No restrictions apply.
std::move(callback).Run(true);
}
void DlpContentManager::AddOrUpdateScreenShare(
const std::string& label,
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
base::RepeatingClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback) {
auto screen_share_it = base::ranges::find_if(
running_screen_shares_,
[&label, media_id](const std::unique_ptr<ScreenShareInfo>& info) {
return info && info->label() == label &&
info->new_media_id() == media_id;
});
if (screen_share_it != running_screen_shares_.end()) {
// This should only happen for tab shares, and only if there was a source
// change.
DCHECK(media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS);
ScreenShareInfo* screen_share = screen_share_it->get();
DCHECK(screen_share->state() ==
ScreenShareInfo::State::kPausedBeforeSourceChange ||
screen_share->state() ==
ScreenShareInfo::State::kRunningBeforeSourceChange);
if (screen_share->state() ==
ScreenShareInfo::State::kPausedBeforeSourceChange) {
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenSharePausedOrResumedUMA, false);
}
screen_share->UpdateAfterSourceChange(
media_id, application_title, std::move(stop_callback),
state_change_callback, source_callback);
} else {
auto screen_share_info = std::make_unique<ScreenShareInfo>(
label, media_id, application_title, std::move(stop_callback),
state_change_callback, source_callback);
running_screen_shares_.push_back(std::move(screen_share_info));
}
}
void DlpContentManager::RemoveScreenShare(
const std::string& label,
const content::DesktopMediaID& media_id) {
std::erase_if(
running_screen_shares_,
[=](const std::unique_ptr<ScreenShareInfo>& screen_share_info) -> bool {
return screen_share_info->label() == label &&
screen_share_info->media_id() == media_id &&
screen_share_info->state() !=
ScreenShareInfo::State::kRunningBeforeSourceChange &&
screen_share_info->state() !=
ScreenShareInfo::State::kPausedBeforeSourceChange;
});
}
void DlpContentManager::CheckRunningScreenShares() {
for (auto& screen_share : running_screen_shares_) {
ConfidentialContentsInfo info = GetScreenShareConfidentialContentsInfo(
screen_share->media_id(), screen_share->web_contents().get());
if (IsReported(info.restriction_info) && reporting_manager_ &&
last_reported_screen_share_.ShouldReportAndUpdate(
screen_share->label(), info.confidential_contents)) {
ReportEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenShare,
info.restriction_info.level, reporting_manager_);
}
// TODO(crbug.com/1326541): Fix for new tab shares.
if (screen_share->GetLatestRestriction() == info.restriction_info &&
screen_share->GetConfidentialContents() == info.confidential_contents) {
// No change in restrictions that apply to this screen share.
// Additional information, such as the titles, might have changed so we
// check if need to update the warning.
if (screen_share->HasOpenDialogWidget()) {
if (EqualWithTitles(screen_share->GetConfidentialContents(),
info.confidential_contents))
continue;
screen_share->set_latest_confidential_contents_info(info);
screen_share->MaybeCloseDialogWidget();
RemoveAllowedContents(info.confidential_contents,
DlpRulesManager::Restriction::kScreenShare);
// base::Unretained(this) is safe here because DlpContentManager is
// initialized as a singleton that's always available in the system.
screen_share->set_dialog_widget(
warn_notifier_->ShowDlpScreenShareWarningDialog(
base::BindOnce(
&DlpContentManager::OnDlpScreenShareWarnDialogReply,
base::Unretained(this), info, screen_share->GetWeakPtr()),
info.confidential_contents, screen_share->application_title()));
}
continue;
}
screen_share->set_latest_confidential_contents_info(info);
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenShareBlockedUMA,
IsBlocked(info.restriction_info));
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenShareWarnedUMA,
IsWarn(info.restriction_info));
if (IsBlocked(info.restriction_info)) {
if (screen_share->state() == ScreenShareInfo::State::kRunning) {
screen_share->Pause();
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenSharePausedOrResumedUMA, true);
screen_share->MaybeUpdateNotifications();
}
continue;
}
if (IsWarn(info.restriction_info)) {
// Close previously opened dialog, if any.
screen_share->MaybeCloseDialogWidget();
// Check which of the contents were already allowed and don't warn for
// those.
RemoveAllowedContents(info.confidential_contents,
DlpRulesManager::Restriction::kScreenShare);
if (info.confidential_contents.IsEmpty()) {
// The user already allowed all the visible content.
if (reporting_manager_ &&
last_reported_screen_share_.ShouldReportAndUpdate(
screen_share->label(), info.confidential_contents)) {
ReportWarningProceededEvent(
info.restriction_info.url,
DlpRulesManager::Restriction::kScreenShare, reporting_manager_);
}
if (screen_share->state() == ScreenShareInfo::State::kPaused) {
screen_share->Resume();
screen_share->MaybeUpdateNotifications();
}
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenShareWarnSilentProceededUMA, true);
continue;
}
if (screen_share->state() == ScreenShareInfo::State::kRunning) {
screen_share->Pause();
screen_share->HideNotifications();
}
ReportWarningEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenShare);
// base::Unretained(this) is safe here because DlpContentManager is
// initialized as a singleton that's always available in the system.
screen_share->set_dialog_widget(
warn_notifier_->ShowDlpScreenShareWarningDialog(
base::BindOnce(
&DlpContentManager::OnDlpScreenShareWarnDialogReply,
base::Unretained(this), info, screen_share->GetWeakPtr()),
info.confidential_contents, screen_share->application_title()));
continue;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DlpContentManager::MaybeResumeScreenShare,
base::Unretained(this), screen_share->GetWeakPtr()),
kScreenShareResumeDelay);
}
}
void DlpContentManager::MaybeResumeScreenShare(
base::WeakPtr<ScreenShareInfo> screen_share) {
if (!screen_share ||
screen_share->state() != ScreenShareInfo::State::kPaused) {
return;
}
ConfidentialContentsInfo info = GetScreenShareConfidentialContentsInfo(
screen_share->media_id(), screen_share->web_contents().get());
if (IsBlocked(info.restriction_info) || IsWarn(info.restriction_info)) {
return;
}
screen_share->Resume();
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenSharePausedOrResumedUMA, false);
screen_share->MaybeUpdateNotifications();
}
void DlpContentManager::OnDlpScreenShareWarnDialogReply(
const ConfidentialContentsInfo& info,
base::WeakPtr<ScreenShareInfo> screen_share,
bool should_proceed) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!screen_share)
// The screen share was stopped before the dialog was addressed, so no need
// to do anything.
return;
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenShareWarnProceededUMA, should_proceed);
if (should_proceed) {
if (reporting_manager_ &&
last_reported_screen_share_.ShouldReportAndUpdate(
screen_share->label(), info.confidential_contents))
ReportWarningProceededEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenShare,
reporting_manager_);
screen_share->Resume();
for (const auto& content : info.confidential_contents.GetContents()) {
user_allowed_contents_cache_.Cache(
content, DlpRulesManager::Restriction::kScreenShare);
}
screen_share->MaybeUpdateNotifications();
} else {
screen_share->Stop();
RemoveScreenShare(screen_share->label(), screen_share->media_id());
}
}
// TODO(1293512): Consider moving reporting of warning proceeded events inside
// OnDlpWarnDialogReply().
void DlpContentManager::OnDlpWarnDialogReply(
const DlpConfidentialContents& confidential_contents,
DlpRulesManager::Restriction restriction,
WarningCallback callback,
bool should_proceed) {
auto suffix = RestrictionToWarnProceededUMASuffix(restriction);
if (suffix.has_value())
data_controls::DlpBooleanHistogram(suffix.value(), should_proceed);
if (should_proceed) {
for (const auto& content : confidential_contents.GetContents()) {
user_allowed_contents_cache_.Cache(content, restriction);
}
}
std::move(callback).Run(should_proceed);
}
void DlpContentManager::MaybeReportEvent(
const RestrictionLevelAndUrl& restriction_info,
DlpRulesManager::Restriction restriction) {
// TODO(crbug.com/1260302): Add reporting and metrics for WARN restrictions.
if (IsReported(restriction_info) && reporting_manager_) {
ReportEvent(restriction_info.url, restriction, restriction_info.level,
reporting_manager_);
}
}
void DlpContentManager::ReportWarningEvent(
const GURL& url,
DlpRulesManager::Restriction restriction) {
if (reporting_manager_) {
ReportEvent(url, restriction, DlpRulesManager::Level::kWarn,
reporting_manager_);
}
}
void DlpContentManager::RemoveAllowedContents(
DlpConfidentialContents& contents,
DlpRulesManager::Restriction restriction) {
base::EraseIf(
contents.GetContents(), [=, this](const DlpConfidentialContent& content) {
return user_allowed_contents_cache_.Contains(content, restriction);
});
}
void DlpContentManager::UpdateConfidentiality(
content::WebContents* web_contents,
const DlpContentRestrictionSet& restriction_set) {
if (restriction_set.IsEmpty()) {
RemoveFromConfidential(web_contents);
} else {
confidential_web_contents_[web_contents] = restriction_set;
}
}
void DlpContentManager::NotifyOnConfidentialityChanged(
const DlpContentRestrictionSet& old_restriction_set,
const DlpContentRestrictionSet& new_restriction_set,
content::WebContents* web_contents) {
for (int i = 0; i <= static_cast<int>(DlpContentRestriction::kMaxValue);
++i) {
auto restriction = static_cast<DlpContentRestriction>(i);
auto old_level = old_restriction_set.GetRestrictionLevel(restriction);
auto new_level = new_restriction_set.GetRestrictionLevel(restriction);
if (old_level == new_level) {
// If there is no change in this restriction, do not notify its
// observers.
continue;
}
auto& observer_list = observer_lists_[static_cast<int>(restriction)];
for (DlpContentManagerObserver& observer : observer_list) {
observer.OnConfidentialityChanged(old_level, new_level, web_contents);
}
}
}
bool DlpContentManager::LastReportedScreenShare::ShouldReportAndUpdate(
const std::string& label,
const DlpConfidentialContents& confidential_contents) {
// Ignore reporting for empty labels. A media streams with an empty label is
// most likely is an audio stream.
if (label.empty())
return false;
if (label != label_) {
label_ = label;
confidential_contents_ = confidential_contents;
return true;
}
// TODO(1306306): Consider reporting all visible confidential urls for
// onscreen restrictions.
if (!std::includes(confidential_contents_.GetContents().begin(),
confidential_contents_.GetContents().end(),
confidential_contents.GetContents().begin(),
confidential_contents.GetContents().end())) {
confidential_contents_.InsertOrUpdate(confidential_contents);
return true;
}
return false;
}
} // namespace policy