blob: ad497daa0d7923e97c99c02d00dd233a9ac06477 [file] [log] [blame]
// Copyright 2020 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/ash/policy/dlp/data_transfer_dlp_controller.h"
#include <string>
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/syslog_logging.h"
#include "chrome/browser/ash/policy/dlp/dlp_histogram_helper.h"
#include "chrome/browser/ash/policy/dlp/dlp_reporting_manager.h"
#include "chrome/browser/ash/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/ash/policy/dlp/dlp_rules_manager_factory.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/constants.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "url/gurl.h"
namespace policy {
namespace {
bool IsFilesApp(const ui::DataTransferEndpoint* const data_dst) {
if (!data_dst || !data_dst->IsUrlType())
return false;
GURL url = data_dst->origin()->GetURL();
return url.has_scheme() && url.SchemeIs(extensions::kExtensionScheme) &&
url.has_host() && url.host() == extension_misc::kFilesManagerAppId;
}
bool IsClipboardHistory(const ui::DataTransferEndpoint* const data_dst) {
return data_dst && data_dst->type() == ui::EndpointType::kClipboardHistory;
}
template <typename T>
void ReportEvent(const DlpRulesManager& dlp_rules_manager,
const std::string& src_pattern,
const T& dst,
DlpRulesManager::Level level) {
if (level != DlpRulesManager::Level::kReport &&
level != DlpRulesManager::Level::kBlock)
return;
auto* reporting_manager = dlp_rules_manager.GetReportingManager();
if (!reporting_manager)
return;
reporting_manager->ReportEvent(
src_pattern, dst, DlpRulesManager::Restriction::kClipboard, level);
}
DlpRulesManager::Level IsDataTransferAllowed(
const DlpRulesManager& dlp_rules_manager,
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst) {
if (!data_src || !data_src->IsUrlType()) { // Currently we only handle URLs.
return DlpRulesManager::Level::kAllow;
}
const GURL src_url = data_src->origin()->GetURL();
ui::EndpointType dst_type =
data_dst ? data_dst->type() : ui::EndpointType::kDefault;
DlpRulesManager::Level level = DlpRulesManager::Level::kAllow;
switch (dst_type) {
case ui::EndpointType::kDefault:
case ui::EndpointType::kUnknownVm:
case ui::EndpointType::kBorealis: {
std::string src_pattern;
std::string dst_pattern;
// Passing empty URL will return restricted if there's a rule restricting
// the src against any dst (*), otherwise it will return ALLOW.
level = dlp_rules_manager.IsRestrictedDestination(
src_url, GURL(), DlpRulesManager::Restriction::kClipboard,
&src_pattern, &dst_pattern);
ReportEvent(dlp_rules_manager, src_pattern, dst_pattern, level);
break;
}
case ui::EndpointType::kUrl: {
GURL dst_url = data_dst->origin()->GetURL();
std::string src_pattern;
std::string dst_pattern;
level = dlp_rules_manager.IsRestrictedDestination(
src_url, dst_url, DlpRulesManager::Restriction::kClipboard,
&src_pattern, &dst_pattern);
if (!IsFilesApp(data_dst))
ReportEvent(dlp_rules_manager, src_pattern, dst_pattern, level);
break;
}
case ui::EndpointType::kCrostini: {
std::string src_pattern;
level = dlp_rules_manager.IsRestrictedComponent(
src_url, DlpRulesManager::Component::kCrostini,
DlpRulesManager::Restriction::kClipboard, &src_pattern);
ReportEvent(dlp_rules_manager, src_pattern,
DlpRulesManager::Component::kCrostini, level);
break;
}
case ui::EndpointType::kPluginVm: {
std::string src_pattern;
level = dlp_rules_manager.IsRestrictedComponent(
src_url, DlpRulesManager::Component::kPluginVm,
DlpRulesManager::Restriction::kClipboard, &src_pattern);
ReportEvent(dlp_rules_manager, src_pattern,
DlpRulesManager::Component::kPluginVm, level);
break;
}
case ui::EndpointType::kArc: {
std::string src_pattern;
level = dlp_rules_manager.IsRestrictedComponent(
src_url, DlpRulesManager::Component::kArc,
DlpRulesManager::Restriction::kClipboard, &src_pattern);
ReportEvent(dlp_rules_manager, src_pattern,
DlpRulesManager::Component::kArc, level);
break;
}
case ui::EndpointType::kClipboardHistory: {
level = DlpRulesManager::Level::kAllow;
break;
}
default:
NOTREACHED();
}
return level;
}
bool ShouldNotifyOnPaste(const ui::DataTransferEndpoint* const data_dst) {
bool notify_on_paste = !data_dst || data_dst->notify_if_restricted();
// Files Apps continuously reads the clipboard data which triggers a lot of
// notifications while the user isn't actually initiating any copy/paste.
// In BLOCK mode, data access by Files app will be denied silently.
// In WARN mode, data access by Files app will be allowed silently.
// TODO(crbug.com/1152475): Find a better way to handle File app.
// When ClipboardHistory tries to read the clipboard we should allow it
// silently.
if (IsFilesApp(data_dst) || IsClipboardHistory(data_dst))
notify_on_paste = false;
return notify_on_paste;
}
} // namespace
// static
void DataTransferDlpController::Init(const DlpRulesManager& dlp_rules_manager) {
if (!HasInstance()) {
DlpBooleanHistogram(dlp::kDataTransferControllerStartedUMA, true);
new DataTransferDlpController(dlp_rules_manager);
}
}
bool DataTransferDlpController::IsClipboardReadAllowed(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst) {
DlpRulesManager::Level level =
IsDataTransferAllowed(dlp_rules_manager_, data_src, data_dst);
bool notify_on_paste = ShouldNotifyOnPaste(data_dst);
bool is_read_allowed = true;
switch (level) {
case DlpRulesManager::Level::kBlock:
if (notify_on_paste) {
SYSLOG(INFO) << "DLP blocked paste from clipboard";
NotifyBlockedPaste(data_src, data_dst);
}
is_read_allowed = false;
break;
case DlpRulesManager::Level::kWarn:
if (notify_on_paste) {
// In case the clipboard data is in warning mode, it will be allowed to
// be shared with Arc, Crostini, and Plugin VM without waiting for the
// user decision.
if (data_dst && (data_dst->type() == ui::EndpointType::kArc ||
data_dst->type() == ui::EndpointType::kPluginVm ||
data_dst->type() == ui::EndpointType::kCrostini)) {
WarnOnPaste(data_src, data_dst);
} else if (ShouldCancelOnWarn(data_dst)) {
is_read_allowed = false;
} else if (!(data_dst && data_dst->IsUrlType()) &&
!ShouldPasteOnWarn(data_dst)) {
SYSLOG(INFO) << "DLP warned on paste from clipboard";
WarnOnPaste(data_src, data_dst);
is_read_allowed = false;
}
}
break;
default:
break;
}
DlpBooleanHistogram(dlp::kClipboardReadBlockedUMA, !is_read_allowed);
return is_read_allowed;
}
void DataTransferDlpController::PasteIfAllowed(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst,
content::WebContents* web_contents,
base::OnceCallback<void(bool)> callback) {
DCHECK(data_dst);
DCHECK(data_dst->IsUrlType());
if (!web_contents) {
std::move(callback).Run(false);
return;
}
DlpRulesManager::Level level =
IsDataTransferAllowed(dlp_rules_manager_, data_src, data_dst);
// If it's blocked, the data should be empty & PasteIfAllowed should not be
// called.
DCHECK_NE(level, DlpRulesManager::Level::kBlock);
if (level == DlpRulesManager::Level::kAllow ||
level == DlpRulesManager::Level::kReport) {
std::move(callback).Run(true);
return;
}
DCHECK_EQ(level, DlpRulesManager::Level::kWarn);
if (ShouldNotifyOnPaste(data_dst)) {
if (ShouldPasteOnWarn(data_dst))
std::move(callback).Run(true);
else if (ShouldCancelOnWarn(data_dst))
std::move(callback).Run(false);
else
WarnOnBlinkPaste(data_src, data_dst, web_contents, std::move(callback));
} else {
std::move(callback).Run(true);
}
}
bool DataTransferDlpController::IsDragDropAllowed(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst,
const bool is_drop) {
DlpRulesManager::Level level =
IsDataTransferAllowed(dlp_rules_manager_, data_src, data_dst);
if (level == DlpRulesManager::Level::kBlock && is_drop) {
SYSLOG(INFO) << "DLP blocked drop of dragged data";
NotifyBlockedDrop(data_src, data_dst);
}
const bool is_drop_allowed = (level == DlpRulesManager::Level::kAllow) ||
(level == DlpRulesManager::Level::kReport);
DlpBooleanHistogram(dlp::kDragDropBlockedUMA, !is_drop_allowed);
return is_drop_allowed;
}
DataTransferDlpController::DataTransferDlpController(
const DlpRulesManager& dlp_rules_manager)
: dlp_rules_manager_(dlp_rules_manager) {}
DataTransferDlpController::~DataTransferDlpController() = default;
void DataTransferDlpController::NotifyBlockedPaste(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst) {
clipboard_notifier_.NotifyBlockedAction(data_src, data_dst);
}
void DataTransferDlpController::WarnOnPaste(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst) {
DCHECK(!(data_dst && data_dst->IsUrlType()));
clipboard_notifier_.WarnOnPaste(data_src, data_dst);
}
void DataTransferDlpController::WarnOnBlinkPaste(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst,
content::WebContents* web_contents,
base::OnceCallback<void(bool)> paste_cb) {
clipboard_notifier_.WarnOnBlinkPaste(data_src, data_dst, web_contents,
std::move(paste_cb));
}
bool DataTransferDlpController::ShouldPasteOnWarn(
const ui::DataTransferEndpoint* const data_dst) {
return clipboard_notifier_.DidUserApproveDst(data_dst);
}
bool DataTransferDlpController::ShouldCancelOnWarn(
const ui::DataTransferEndpoint* const data_dst) {
return clipboard_notifier_.DidUserCancelDst(data_dst);
}
void DataTransferDlpController::NotifyBlockedDrop(
const ui::DataTransferEndpoint* const data_src,
const ui::DataTransferEndpoint* const data_dst) {
drag_drop_notifier_.NotifyBlockedAction(data_src, data_dst);
}
} // namespace policy