// Copyright 2020 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/ash/policy/dlp/dlp_content_manager_ash.h"
#include <memory>
#include <string>
#include <vector>
#include "ash/public/cpp/privacy_screen_dlp_helper.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/ash/crosapi/window_util.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_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/ash/capture_mode/chrome_capture_mode_delegate.h"
#include "components/enterprise/data_controls/core/browser/dlp_histogram_helper.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "url/gurl.h"
namespace policy {
namespace {
// Delay to wait to turn off privacy screen enforcement after confidential data
// becomes not visible. This is done to not blink the privacy screen in case of
// a quick switch from one confidential data to another.
const base::TimeDelta kPrivacyScreenOffDelay = base::Milliseconds(500);
// 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);
}
// If there is an on going video recording, interrupts it and notifies the user.
void InterruptVideoRecording() {
if (ChromeCaptureModeDelegate::Get()->InterruptVideoRecordingIfAny())
ShowDlpVideoCaptureStoppedNotification();
}
bool IsAnyChildVisible(aura::Window* window) {
if (window->GetOcclusionState() == aura::Window::OcclusionState::VISIBLE)
return true;
for (aura::Window* child : window->children()) {
if (IsAnyChildVisible(child))
return true;
}
return false;
}
// Retrieves a child representing ExoSurface.
aura::Window* FindSurface(aura::Window* window) {
if (!window)
return nullptr;
if (exo::Surface::AsSurface(window))
return window;
for (aura::Window* child : window->children()) {
auto* found_window = FindSurface(child);
if (found_window)
return found_window;
}
return nullptr;
}
} // namespace
static DlpContentManagerAsh* g_dlp_content_manager = nullptr;
// static
DlpContentManagerAsh* DlpContentManagerAsh::Get() {
if (g_dlp_content_manager)
return g_dlp_content_manager;
return static_cast<DlpContentManagerAsh*>(DlpContentObserver::Get());
}
void DlpContentManagerAsh::OnWindowOcclusionChanged(aura::Window* window) {
MaybeChangeOnScreenRestrictions();
}
void DlpContentManagerAsh::OnWindowDestroying(aura::Window* window) {
surface_observers_.erase(window);
window_observers_.erase(window);
confidential_windows_.erase(window);
MaybeChangeOnScreenRestrictions();
}
void DlpContentManagerAsh::OnWindowTitleChanged(aura::Window* window) {
CheckRunningVideoCapture();
CheckRunningScreenShares();
}
DlpContentRestrictionSet DlpContentManagerAsh::GetOnScreenPresentRestrictions()
const {
return on_screen_restrictions_;
}
void DlpContentManagerAsh::CheckScreenshotRestriction(
const ScreenshotArea& area,
ash::OnCaptureModeDlpRestrictionChecked callback) {
const ConfidentialContentsInfo info =
GetAreaConfidentialContentsInfo(area, DlpContentRestriction::kScreenshot);
if (IsBlocked(info.restriction_info)) {
MaybeReportEvent(info.restriction_info,
DlpRulesManager::Restriction::kScreenshot);
}
data_controls::DlpBooleanHistogram(data_controls::dlp::kScreenshotBlockedUMA,
IsBlocked(info.restriction_info));
data_controls::DlpBooleanHistogram(data_controls::dlp::kScreenshotWarnedUMA,
IsWarn(info.restriction_info));
CheckScreenCaptureRestriction(info, std::move(callback));
}
void DlpContentManagerAsh::CheckScreenShareRestriction(
const content::DesktopMediaID& media_id,
const std::u16string& application_title,
WarningCallback callback) {
ConfidentialContentsInfo info = GetScreenShareConfidentialContentsInfo(
media_id, GetWebContentsFromMediaId(media_id));
ProcessScreenShareRestriction(application_title, info, std::move(callback));
}
void DlpContentManagerAsh::OnVideoCaptureStarted(const ScreenshotArea& area) {
DCHECK(!running_video_capture_info_.has_value());
running_video_capture_info_.emplace(area);
const ConfidentialContentsInfo info =
GetAreaConfidentialContentsInfo(area, DlpContentRestriction::kScreenshot);
// Taking video capture of confidential content with block level restriction
// should not proceed to this function. Taking video capture should be blocked
// earlier.
DCHECK(!IsBlocked(info.restriction_info));
if (IsReported(info.restriction_info)) {
// Don't report for the report mode before starting a video capture to avoid
// reporting multiple times.
DCHECK(
running_video_capture_info_->reported_confidential_contents.IsEmpty());
// TODO(1306306): Consider reporting all visible confidential urls for
// onscreen restrictions.
MaybeReportEvent(info.restriction_info,
DlpRulesManager::Restriction::kScreenshot);
running_video_capture_info_->reported_confidential_contents.InsertOrUpdate(
info.confidential_contents);
}
if (IsWarn(info.restriction_info) && reporting_manager_) {
ReportWarningProceededEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenshot,
reporting_manager_);
}
}
void DlpContentManagerAsh::CheckStoppedVideoCapture(
ash::OnCaptureModeDlpRestrictionChecked callback) {
if (!running_video_capture_info_.has_value()) {
std::move(callback).Run(/*proceed=*/true);
return;
}
// If some confidential content was shown during the recording, but not
// before, warn the user before saving the file.
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenshotWarnedUMA,
running_video_capture_info_->had_warning_restriction);
if (!running_video_capture_info_->confidential_contents.IsEmpty()) {
const GURL& url =
running_video_capture_info_->confidential_contents.GetContents()
.begin()
->url;
ReportWarningEvent(url, DlpRulesManager::Restriction::kScreenshot);
auto reporting_callback = base::BindOnce(
&MaybeReportWarningProceededEvent, url,
DlpRulesManager::Restriction::kScreenshot, reporting_manager_);
// base::Unretained(this) is safe here because DlpContentManagerAsh is
// initialized as a singleton that's always available in the system.
warn_notifier_->ShowDlpVideoCaptureWarningDialog(
base::BindOnce(&DlpContentManagerAsh::OnDlpWarnDialogReply,
base::Unretained(this),
running_video_capture_info_->confidential_contents,
DlpRulesManager::Restriction::kScreenshot,
std::move(reporting_callback).Then(std::move(callback))),
running_video_capture_info_->confidential_contents);
} else {
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenshotWarnSilentProceededUMA, true);
std::move(callback).Run(/*proceed=*/true);
}
running_video_capture_info_.reset();
}
void DlpContentManagerAsh::OnImageCapture(const ScreenshotArea& area) {
const ConfidentialContentsInfo info =
GetAreaConfidentialContentsInfo(area, DlpContentRestriction::kScreenshot);
// Taking screenshots of confidential content with block level restriction
// should not proceed to this function. Taking screenshot should be blocked
// earlier.
DCHECK(!IsBlocked(info.restriction_info));
if (IsReported(info.restriction_info)) {
MaybeReportEvent(info.restriction_info,
DlpRulesManager::Restriction::kScreenshot);
}
if (IsWarn(info.restriction_info) && reporting_manager_) {
ReportWarningProceededEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenshot,
reporting_manager_);
}
}
void DlpContentManagerAsh::CheckCaptureModeInitRestriction(
ash::OnCaptureModeDlpRestrictionChecked callback) {
const ConfidentialContentsInfo info =
GetConfidentialContentsOnScreen(DlpContentRestriction::kScreenshot);
if (IsBlocked(info.restriction_info)) {
MaybeReportEvent(info.restriction_info,
DlpRulesManager::Restriction::kScreenshot);
}
data_controls::DlpBooleanHistogram(
data_controls::dlp::kCaptureModeInitBlockedUMA,
IsBlocked(info.restriction_info));
data_controls::DlpBooleanHistogram(
data_controls::dlp::kCaptureModeInitWarnedUMA,
IsWarn(info.restriction_info));
CheckScreenCaptureRestriction(info, std::move(callback));
}
void DlpContentManagerAsh::OnScreenShareStarted(
const std::string& label,
std::vector<content::DesktopMediaID> screen_share_ids,
const std::u16string& application_title,
base::RepeatingClosure stop_callback,
content::MediaStreamUI::StateChangeCallback state_change_callback,
content::MediaStreamUI::SourceCallback source_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (const content::DesktopMediaID& id : screen_share_ids) {
AddOrUpdateScreenShare(label, id, application_title, stop_callback,
state_change_callback, source_callback);
}
CheckRunningScreenShares();
}
void DlpContentManagerAsh::OnScreenShareStopped(
const std::string& label,
const content::DesktopMediaID& media_id) {
RemoveScreenShare(label, media_id);
}
// TODO(b/308912502): migrate from mojo crosapi to wayland IPC. The current
// implementation depends on the client keeping the connection for its whole
// lifetime.
void DlpContentManagerAsh::OnWindowRestrictionChanged(
mojo::ReceiverId receiver_id,
const std::string& window_id,
const DlpContentRestrictionSet& restrictions) {
aura::Window* window = crosapi::GetShellSurfaceWindow(window_id);
if (window) {
pending_restrictions_.erase(window_id);
confidential_windows_[window] = restrictions;
window_observers_[window] =
std::make_unique<DlpWindowObserver>(window, this);
aura::Window* surface = FindSurface(window);
if (surface) {
surface_observers_[window] =
std::make_unique<DlpWindowObserver>(surface, this);
}
MaybeChangeOnScreenRestrictions();
} else {
if (restrictions.IsEmpty()) {
pending_restrictions_.erase(window_id);
auto iter = pending_restrictions_owner_.find(receiver_id);
if (iter != pending_restrictions_owner_.end()) {
iter->second.erase(window_id);
if (iter->second.empty()) {
pending_restrictions_owner_.erase(iter);
}
}
} else {
pending_restrictions_.insert({window_id, {receiver_id, restrictions}});
auto [iter, is_new] =
pending_restrictions_owner_.try_emplace(receiver_id);
iter->second.insert(window_id);
}
}
}
void DlpContentManagerAsh::OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!gained_active) {
return;
}
const std::string* application_id = exo::GetShellApplicationId(gained_active);
if (!application_id) {
return;
}
auto iter = pending_restrictions_.find(*application_id);
if (iter != pending_restrictions_.end()) {
auto [receiver_id, restrictions] =
pending_restrictions_.extract(iter).mapped();
OnWindowRestrictionChanged(receiver_id, *application_id, restrictions);
}
}
void DlpContentManagerAsh::CleanPendingRestrictions(
mojo::ReceiverId receiver_id) {
auto iter = pending_restrictions_owner_.find(receiver_id);
if (iter == pending_restrictions_owner_.end()) {
return;
}
for (const auto& window_id : iter->second) {
pending_restrictions_.erase(window_id);
}
pending_restrictions_owner_.erase(iter);
}
DlpContentManagerAsh::VideoCaptureInfo::VideoCaptureInfo(
const ScreenshotArea& area)
: area(area) {}
DlpContentManagerAsh::DlpContentManagerAsh() {
if (ash::Shell::HasInstance() && ash::Shell::Get()->activation_client()) {
window_activation_observation_.Observe(
ash::Shell::Get()->activation_client());
}
}
DlpContentManagerAsh::~DlpContentManagerAsh() = default;
void DlpContentManagerAsh::OnConfidentialityChanged(
content::WebContents* web_contents,
const DlpContentRestrictionSet& restriction_set) {
DlpContentManager::OnConfidentialityChanged(web_contents, restriction_set);
if (!restriction_set.IsEmpty()) {
web_contents_window_observers_[web_contents] =
std::make_unique<DlpWindowObserver>(web_contents->GetNativeView(),
this);
if (web_contents->GetVisibility() == content::Visibility::VISIBLE) {
MaybeChangeOnScreenRestrictions();
} else {
CheckRunningScreenShares();
}
} else {
CheckRunningScreenShares();
}
}
void DlpContentManagerAsh::OnVisibilityChanged(
content::WebContents* web_contents) {
MaybeChangeOnScreenRestrictions();
}
void DlpContentManagerAsh::RemoveFromConfidential(
content::WebContents* web_contents) {
DlpContentManager::RemoveFromConfidential(web_contents);
web_contents_window_observers_.erase(web_contents);
MaybeChangeOnScreenRestrictions();
}
void DlpContentManagerAsh::MaybeChangeOnScreenRestrictions() {
DlpContentRestrictionSet new_restriction_set;
// Check each visible WebContents.
for (const auto& entry : confidential_web_contents_) {
if (entry.first->GetVisibility() == content::Visibility::VISIBLE) {
new_restriction_set.UnionWith(entry.second);
}
}
// Check each visible Lacros window.
for (const auto& entry : confidential_windows_) {
if (entry.first->IsVisible()) {
new_restriction_set.UnionWith(entry.second);
}
}
if (on_screen_restrictions_ != new_restriction_set) {
DlpContentRestrictionSet added_restrictions =
new_restriction_set.DifferenceWith(on_screen_restrictions_);
DlpContentRestrictionSet removed_restrictions =
on_screen_restrictions_.DifferenceWith(new_restriction_set);
on_screen_restrictions_ = new_restriction_set;
OnScreenRestrictionsChanged(added_restrictions, removed_restrictions);
}
CheckRunningVideoCapture();
CheckRunningScreenShares();
}
void DlpContentManagerAsh::OnScreenRestrictionsChanged(
const DlpContentRestrictionSet& added_restrictions,
const DlpContentRestrictionSet& removed_restrictions) {
DCHECK(!(added_restrictions.GetRestrictionLevel(
DlpContentRestriction::kPrivacyScreen) ==
DlpRulesManager::Level::kBlock &&
removed_restrictions.GetRestrictionLevel(
DlpContentRestriction::kPrivacyScreen) ==
DlpRulesManager::Level::kBlock));
ash::PrivacyScreenDlpHelper* privacy_screen_helper =
ash::PrivacyScreenDlpHelper::Get();
if (!privacy_screen_helper->IsSupported())
return;
const RestrictionLevelAndUrl added_restriction_info =
added_restrictions.GetRestrictionLevelAndUrl(
DlpContentRestriction::kPrivacyScreen);
if (added_restriction_info.level == DlpRulesManager::Level::kBlock) {
data_controls::DlpBooleanHistogram(
data_controls::dlp::kPrivacyScreenEnforcedUMA, true);
privacy_screen_helper->SetEnforced(true);
}
MaybeReportEvent(added_restriction_info,
DlpRulesManager::Restriction::kPrivacyScreen);
if (removed_restrictions.GetRestrictionLevel(
DlpContentRestriction::kPrivacyScreen) ==
DlpRulesManager::Level::kBlock) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&DlpContentManagerAsh::MaybeRemovePrivacyScreenEnforcement,
base::Unretained(this)),
kPrivacyScreenOffDelay);
}
}
void DlpContentManagerAsh::MaybeRemovePrivacyScreenEnforcement() const {
if (GetOnScreenPresentRestrictions().GetRestrictionLevel(
DlpContentRestriction::kPrivacyScreen) !=
DlpRulesManager::Level::kBlock) {
data_controls::DlpBooleanHistogram(
data_controls::dlp::kPrivacyScreenEnforcedUMA, false);
ash::PrivacyScreenDlpHelper::Get()->SetEnforced(false);
}
}
DlpContentManagerAsh::ConfidentialContentsInfo
DlpContentManagerAsh::GetConfidentialContentsOnScreen(
DlpContentRestriction restriction) const {
DlpContentManagerAsh::ConfidentialContentsInfo info;
info.restriction_info =
GetOnScreenPresentRestrictions().GetRestrictionLevelAndUrl(restriction);
for (auto& entry : confidential_web_contents_) {
if (entry.first->GetVisibility() != content::Visibility::VISIBLE)
continue;
if (entry.first->IsBeingDestroyed()) {
// The contents can be in the process of being destroyed during this
// check, although they have not yet been removed from
// confidential_web_contents_. For example, this happens when we trigger
// the check from OnWebContentsDestroyed().
continue;
}
if (entry.second.GetRestrictionLevel(restriction) ==
info.restriction_info.level) {
info.confidential_contents.Add(entry.first);
}
}
for (auto& entry : confidential_windows_) {
if (!entry.first->IsVisible() || !IsAnyChildVisible(entry.first))
continue;
if (entry.first->is_destroying()) {
// The window can be in the process of being destroyed during this
// check, although it has not yet been removed from
// confidential_windows. For example, this happens when we trigger
// the check from OnWindowDestroying().
continue;
}
if (entry.second.GetRestrictionLevel(restriction) ==
info.restriction_info.level) {
info.confidential_contents.Add(
entry.first, entry.second.GetRestrictionUrl(restriction));
}
}
return info;
}
DlpContentManagerAsh::ConfidentialContentsInfo
DlpContentManagerAsh::GetAreaConfidentialContentsInfo(
const ScreenshotArea& area,
DlpContentRestriction restriction) const {
DlpContentManagerAsh::ConfidentialContentsInfo info;
// Fullscreen - restricted if any confidential data is visible.
if (area.type == ScreenshotType::kAllRootWindows) {
return GetConfidentialContentsOnScreen(restriction);
}
// Window - restricted if the window contains confidential data.
if (area.type == ScreenshotType::kWindow) {
DCHECK(area.window);
// Check whether the captured window contains any confidential WebContents.
for (auto& entry : confidential_web_contents_) {
aura::Window* web_contents_window = entry.first->GetNativeView();
if (area.window->Contains(web_contents_window)) {
if (entry.second.GetRestrictionLevel(restriction) ==
info.restriction_info.level) {
info.confidential_contents.Add(entry.first);
} else if (entry.second.GetRestrictionLevel(restriction) >
info.restriction_info.level) {
info.restriction_info =
entry.second.GetRestrictionLevelAndUrl(restriction);
info.confidential_contents.ClearAndAdd(entry.first);
}
}
}
// Check whether the captured window is a confidential Lacros window.
auto window_entry = confidential_windows_.find(area.window);
if (window_entry != confidential_windows_.end()) {
if (window_entry->second.GetRestrictionLevel(restriction) ==
info.restriction_info.level) {
info.confidential_contents.Add(
window_entry->first,
window_entry->second.GetRestrictionUrl(restriction));
} else if (window_entry->second.GetRestrictionLevel(restriction) >
info.restriction_info.level) {
info.restriction_info =
window_entry->second.GetRestrictionLevelAndUrl(restriction);
info.confidential_contents.ClearAndAdd(
window_entry->first,
window_entry->second.GetRestrictionUrl(restriction));
}
}
return info;
}
DCHECK_EQ(area.type, ScreenshotType::kPartialWindow);
DCHECK(area.rect);
DCHECK(area.window);
// Partial - restricted if any visible confidential content intersects
// with the area.
// Intersect the captured area with all confidential WebContents.
for (auto& entry : confidential_web_contents_) {
if (entry.first->GetVisibility() != content::Visibility::VISIBLE ||
entry.second.GetRestrictionLevel(restriction) ==
DlpRulesManager::Level::kNotSet) {
continue;
}
aura::Window* web_contents_window = entry.first->GetNativeView();
if (web_contents_window->GetOcclusionState() ==
aura::Window::OcclusionState::OCCLUDED) {
continue;
}
aura::Window* root_window = web_contents_window->GetRootWindow();
// If no root window, then the WebContent shouldn't be visible.
if (!root_window)
continue;
// Not allowing if the area intersects with confidential WebContents,
// but the intersection doesn't belong to occluded area.
gfx::Rect intersection(*area.rect);
aura::Window::ConvertRectToTarget(area.window, root_window, &intersection);
intersection.Intersect(web_contents_window->GetBoundsInRootWindow());
if (intersection.IsEmpty() ||
web_contents_window->occluded_region_in_root().contains(
gfx::RectToSkIRect(intersection)))
continue;
if (entry.second.GetRestrictionLevel(restriction) ==
info.restriction_info.level) {
info.confidential_contents.Add(entry.first);
} else if (entry.second.GetRestrictionLevel(restriction) >
info.restriction_info.level) {
info.restriction_info =
entry.second.GetRestrictionLevelAndUrl(restriction);
info.confidential_contents.ClearAndAdd(entry.first);
}
}
// Intersect the captured area with all confidential Lacros windows.
for (auto& entry : confidential_windows_) {
if (!entry.first->IsVisible() ||
entry.first->GetOcclusionState() ==
aura::Window::OcclusionState::OCCLUDED ||
entry.second.GetRestrictionLevel(restriction) ==
DlpRulesManager::Level::kNotSet) {
continue;
}
aura::Window* root_window = entry.first->GetRootWindow();
// If no root window, then the Window shouldn't be visible.
if (!root_window)
continue;
// Not allowing if the area intersects with confidential Window,
// but the intersection doesn't belong to occluded area.
gfx::Rect intersection(*area.rect);
aura::Window::ConvertRectToTarget(area.window, root_window, &intersection);
intersection.Intersect(entry.first->GetBoundsInRootWindow());
if (intersection.IsEmpty() ||
entry.first->occluded_region_in_root().contains(
gfx::RectToSkIRect(intersection)))
continue;
if (entry.second.GetRestrictionLevel(restriction) ==
info.restriction_info.level) {
info.confidential_contents.Add(
entry.first, entry.second.GetRestrictionUrl(restriction));
} else if (entry.second.GetRestrictionLevel(restriction) >
info.restriction_info.level) {
info.restriction_info =
entry.second.GetRestrictionLevelAndUrl(restriction);
info.confidential_contents.ClearAndAdd(
entry.first, entry.second.GetRestrictionUrl(restriction));
}
}
return info;
}
DlpContentManager::ConfidentialContentsInfo
DlpContentManagerAsh::GetScreenShareConfidentialContentsInfo(
const content::DesktopMediaID& media_id,
content::WebContents* web_contents) const {
if (media_id.type == content::DesktopMediaID::Type::TYPE_SCREEN) {
return GetConfidentialContentsOnScreen(DlpContentRestriction::kScreenShare);
}
if (media_id.type == content::DesktopMediaID::Type::TYPE_WEB_CONTENTS) {
return GetScreenShareConfidentialContentsInfoForWebContents(web_contents);
}
DCHECK_EQ(media_id.type, content::DesktopMediaID::Type::TYPE_WINDOW);
ConfidentialContentsInfo info;
aura::Window* window = content::DesktopMediaID::GetNativeWindowById(media_id);
if (window) {
// Check whether the captured window contains any confidential WebContents.
for (auto& entry : confidential_web_contents_) {
aura::Window* web_contents_window = entry.first->GetNativeView();
if (!window->Contains(web_contents_window))
continue;
if (entry.second.GetRestrictionLevel(
DlpContentRestriction::kScreenShare) ==
info.restriction_info.level) {
info.confidential_contents.Add(entry.first);
} else if (entry.second.GetRestrictionLevel(
DlpContentRestriction::kScreenShare) >
info.restriction_info.level) {
info.restriction_info = entry.second.GetRestrictionLevelAndUrl(
DlpContentRestriction::kScreenShare);
info.confidential_contents.ClearAndAdd(entry.first);
}
}
// Check whether the captured window has a confidential Lacros window.
for (auto& entry : confidential_windows_) {
if (!window->Contains(entry.first))
continue;
if (entry.second.GetRestrictionLevel(
DlpContentRestriction::kScreenShare) ==
info.restriction_info.level) {
info.confidential_contents.Add(
entry.first, entry.second.GetRestrictionUrl(
DlpContentRestriction::kScreenShare));
} else if (entry.second.GetRestrictionLevel(
DlpContentRestriction::kScreenShare) >
info.restriction_info.level) {
info.restriction_info = entry.second.GetRestrictionLevelAndUrl(
DlpContentRestriction::kScreenShare);
info.confidential_contents.ClearAndAdd(
entry.first, entry.second.GetRestrictionUrl(
DlpContentRestriction::kScreenShare));
}
}
}
return info;
}
void DlpContentManagerAsh::TabLocationMaybeChanged(
content::WebContents* web_contents) {
CheckRunningVideoCapture();
CheckRunningScreenShares();
}
void DlpContentManagerAsh::CheckRunningVideoCapture() {
if (!running_video_capture_info_.has_value())
return;
ConfidentialContentsInfo info = GetAreaConfidentialContentsInfo(
running_video_capture_info_->area, DlpContentRestriction::kScreenshot);
if (IsReported(info.restriction_info) &&
!std::includes(running_video_capture_info_->reported_confidential_contents
.GetContents()
.begin(),
running_video_capture_info_->reported_confidential_contents
.GetContents()
.end(),
info.confidential_contents.GetContents().begin(),
info.confidential_contents.GetContents().end())) {
// TODO(1306306): Consider reporting all visible confidential urls for
// onscreen restrictions.
MaybeReportEvent(info.restriction_info,
DlpRulesManager::Restriction::kScreenshot);
running_video_capture_info_->reported_confidential_contents.InsertOrUpdate(
info.confidential_contents);
}
if (IsBlocked(info.restriction_info)) {
data_controls::DlpBooleanHistogram(
data_controls::dlp::kVideoCaptureInterruptedUMA, true);
InterruptVideoRecording();
running_video_capture_info_.reset();
return;
}
if (IsWarn(info.restriction_info)) {
// Remember any confidential content captured during the recording, so we
// can inform the user about it after the recording is finished. We drop
// those that the user was already warned about and has allowed the screen
// capture to proceed.
RemoveAllowedContents(info.confidential_contents,
DlpRulesManager::Restriction::kScreenshot);
running_video_capture_info_->confidential_contents.InsertOrUpdate(
info.confidential_contents);
running_video_capture_info_->had_warning_restriction = true;
return;
}
}
// static
base::TimeDelta DlpContentManagerAsh::GetPrivacyScreenOffDelayForTesting() {
return kPrivacyScreenOffDelay;
}
void DlpContentManagerAsh::CheckScreenCaptureRestriction(
ConfidentialContentsInfo info,
ash::OnCaptureModeDlpRestrictionChecked callback) {
if (IsBlocked(info.restriction_info)) {
// TODO(296534642): Remove once proper tooling is added.
LOG(WARNING) << "Screenshot blocked due to following URL(s) visible:";
for (const auto& content : info.confidential_contents.GetContents()) {
LOG(WARNING) << content.url;
}
ShowDlpScreenCaptureDisabledNotification();
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::kScreenshot);
if (info.confidential_contents.IsEmpty()) {
// The user already allowed all the visible content.
data_controls::DlpBooleanHistogram(
data_controls::dlp::kScreenshotWarnSilentProceededUMA, true);
std::move(callback).Run(true);
return;
}
ReportWarningEvent(info.restriction_info.url,
DlpRulesManager::Restriction::kScreenshot);
// base::Unretained(this) is safe here because DlpContentManagerAsh is
// initialized as a singleton that's always available in the system.
warn_notifier_->ShowDlpScreenCaptureWarningDialog(
base::BindOnce(&DlpContentManagerAsh::OnDlpWarnDialogReply,
base::Unretained(this), info.confidential_contents,
DlpRulesManager::Restriction::kScreenshot,
std::move(callback)),
info.confidential_contents);
return;
}
// No restrictions apply.
std::move(callback).Run(true);
}
} // namespace policy