1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
// Copyright 2023 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/enterprise/data_protection/print_utils.h"
#include <cstring>
#include <optional>
#include <utility>
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "chrome/browser/profiles/profile.h"
#include "components/device_event_log/device_event_log.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "content/public/browser/web_contents.h"
#include "printing/printing_features.h"
namespace enterprise_data_protection {
namespace {
bool ShouldScan(PrintScanningContext context) {
switch (context) {
// For "normal" prints, the scanning can happen immediately after the user
// clicks "Print" in the print preview dialog as the preview document is
// representative of what they are printing.
case PrintScanningContext::kNormalPrintAfterPreview:
return true;
case PrintScanningContext::kNormalPrintBeforePrintDocument:
return false;
// For "system dialog" prints, the scanning waits until the user picks
// settings from the system dialog, but starts applying enterprise-logic
// logic at the `kBeforeSystemDialog` context for that to happen.
//
// Scanning also happens right before the document is printed through an
// existing print job when that is triggered after the print preview
// dialog.
case PrintScanningContext::kBeforeSystemDialog:
return true;
case PrintScanningContext::kSystemPrintAfterPreview:
return false;
case PrintScanningContext::kSystemPrintBeforePrintDocument:
return true;
#if BUILDFLAG(IS_MAC)
// For the "Open PDF in Preview" option on Mac, scan right as it happens
// from the print preview dialog.
case PrintScanningContext::kOpenPdfInPreview:
return true;
#endif // BUILDFLAG(IS_MAC)
}
}
} // namespace
void PrintIfAllowedByPolicy(scoped_refptr<base::RefCountedMemory> print_data,
content::WebContents* initiator,
std::string printer_name,
PrintScanningContext context,
base::OnceCallback<void(bool)> on_verdict,
base::OnceClosure hide_preview) {
// In some cases like the web contents closing or the render process crashing,
// tt's possible for `initiator` to be null. In that case, printing can simply
// be aborted.
if (!initiator) {
std::move(on_verdict).Run(/*allowed=*/false);
return;
}
// This needs to be done to avoid having an embedded page compare against
// policies and report its URL. This is especially important when Chrome's PDF
// reader is used, as a cryptic extension URL will be sent for
// scanning/reporting in that case.
// TODO(b/289243948): Add browser test coverage for web contents received and
// passed to the delegate.
content::WebContents* web_contents = initiator->GetOutermostWebContents();
auto scanning_data = GetPrintAnalysisData(web_contents, context);
if (!scanning_data) {
std::move(on_verdict).Run(/*allowed=*/true);
return;
}
// Populate print metadata.
scanning_data->printer_name = std::move(printer_name);
// Hide the preview dialog so it doesn't cover the content analysis dialog
// showing the status of the scanning. Since that scanning dialog only appears
// when the policy is set to be blocking, don't run `hide_preview` for other
// policy configurations to avoid races between the dialog being hidden vs
// destroyed.
if (scanning_data->settings.block_until_verdict ==
enterprise_connectors::BlockUntilVerdict::kBlock) {
std::move(hide_preview).Run();
}
PrintIfAllowedByPolicy(print_data, web_contents, std::move(*scanning_data),
std::move(on_verdict));
}
void PrintIfAllowedByPolicy(
scoped_refptr<base::RefCountedMemory> print_data,
content::WebContents* initiator,
enterprise_connectors::ContentAnalysisDelegate::Data scanning_data,
base::OnceCallback<void(bool)> on_verdict) {
// The preview document bytes are copied so that the content analysis code
// can arbitrarily use them without having to handle ownership issues with
// other printing code.
base::MappedReadOnlyRegion region =
base::ReadOnlySharedMemoryRegion::Create(print_data->size());
if (!region.IsValid()) {
// Allow printing if the scan can't happen due to memory failure.
PRINTER_LOG(ERROR) << "Printed without analysis due to memory failure";
std::move(on_verdict).Run(/*allowed=*/true);
return;
}
region.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(*print_data);
scanning_data.page = std::move(region.region);
auto on_scan_result = base::BindOnce(
[](base::OnceCallback<void(bool should_proceed)> callback,
const enterprise_connectors::ContentAnalysisDelegate::Data& data,
enterprise_connectors::ContentAnalysisDelegate::Result& result) {
std::move(callback).Run(result.page_result);
},
std::move(on_verdict));
// This needs to be done to avoid having an embedded page compare against
// policies and report its URL. This is especially important when Chrome's PDF
// reader is used, as a cryptic extension URL will be sent for
// scanning/reporting in that case.
// TODO(b/289243948): Add browser test coverage for web contents received and
// passed to the delegate.
content::WebContents* web_contents = initiator->GetOutermostWebContents();
enterprise_connectors::ContentAnalysisDelegate::CreateForWebContents(
web_contents, std::move(scanning_data), std::move(on_scan_result),
safe_browsing::DeepScanAccessPoint::PRINT);
}
std::optional<enterprise_connectors::ContentAnalysisDelegate::Data>
GetPrintAnalysisData(content::WebContents* web_contents,
PrintScanningContext context) {
enterprise_connectors::ContentAnalysisDelegate::Data scanning_data;
bool enabled = enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
Profile::FromBrowserContext(web_contents->GetBrowserContext()),
web_contents->GetOutermostWebContents()->GetLastCommittedURL(),
&scanning_data, enterprise_connectors::AnalysisConnector::PRINT);
if (enabled && ShouldScan(context)) {
switch (context) {
#if BUILDFLAG(IS_MAC)
case PrintScanningContext::kOpenPdfInPreview:
#endif // BUILDFLAG(IS_MAC)
case PrintScanningContext::kNormalPrintAfterPreview:
case PrintScanningContext::kNormalPrintBeforePrintDocument:
scanning_data.reason =
enterprise_connectors::ContentAnalysisRequest::PRINT_PREVIEW_PRINT;
break;
case PrintScanningContext::kBeforeSystemDialog:
case PrintScanningContext::kSystemPrintAfterPreview:
case PrintScanningContext::kSystemPrintBeforePrintDocument:
scanning_data.reason =
enterprise_connectors::ContentAnalysisRequest::SYSTEM_DIALOG_PRINT;
break;
}
return scanning_data;
}
return std::nullopt;
}
} // namespace enterprise_data_protection
|