blob: 716648f2e4a9ada98b5dd3a8f396765da61da7b6 [file] [log] [blame]
// 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 "base/containers/span.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/connectors/analysis/page_print_request_handler.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h"
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/printing/print_preview_test.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/enterprise/connectors/core/reporting_constants.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "content/public/test/browser_task_environment.h"
#include "printing/printing_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/test/fake_content_analysis_sdk_manager.h" // nogncheck
#endif
namespace enterprise_data_protection {
namespace {
using ContentAnalysisResponse = enterprise_connectors::ContentAnalysisResponse;
using ContentAnalysisDelegate = enterprise_connectors::ContentAnalysisDelegate;
using RealtimeReportingClientFactory =
enterprise_connectors::RealtimeReportingClientFactory;
ContentAnalysisDelegate* test_delegate_ = nullptr;
constexpr char kDmToken[] = "dm_token";
constexpr char kLocalPolicy[] = R"(
{
"service_provider": "local_user_agent",
"block_until_verdict": 1,
"enable": [
{
"url_list": ["*"],
"tags": ["dlp"]
}
]
})";
constexpr char kCloudPolicy[] = R"(
{
"service_provider": "google",
"block_until_verdict": 1,
"enable": [
{
"url_list": ["*"],
"tags": ["dlp"]
}
]
})";
constexpr char kScanId[] = "scan_id";
constexpr char kTestData[] = "lalilulelo";
constexpr char kPrinterName[] = "my_printer";
constexpr char kUserName[] = "test-user@chromium.org";
constexpr char16_t kUserJustification[] = u"User justification";
scoped_refptr<base::RefCountedMemory> CreateData() {
return base::MakeRefCounted<base::RefCountedStaticMemory>(
base::byte_span_from_cstring(kTestData));
}
const std::set<std::string>* PrintMimeTypes() {
static std::set<std::string> set = {""};
return &set;
}
ContentAnalysisResponse::Result CreateResult(
ContentAnalysisResponse::Result::TriggeredRule::Action action) {
ContentAnalysisResponse::Result result;
result.set_tag("dlp");
result.set_status(ContentAnalysisResponse::Result::SUCCESS);
if (action !=
ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED) {
auto* rule = result.add_triggered_rules();
rule->set_rule_name("print_rule_name");
rule->set_action(action);
}
return result;
}
ContentAnalysisResponse CreateResponse(
ContentAnalysisResponse::Result::TriggeredRule::Action action) {
ContentAnalysisResponse response;
response.set_request_token(kScanId);
auto* result = response.add_results();
*result = CreateResult(action);
return response;
}
class PrintTestContentAnalysisDelegate : public ContentAnalysisDelegate {
public:
using ContentAnalysisDelegate::ContentAnalysisDelegate;
static std::unique_ptr<ContentAnalysisDelegate> Create(
content::WebContents* contents,
ContentAnalysisDelegate::Data data,
ContentAnalysisDelegate::CompletionCallback callback) {
auto delegate = base::WrapUnique(new PrintTestContentAnalysisDelegate(
contents, std::move(data), std::move(callback),
enterprise_connectors::DeepScanAccessPoint::PRINT));
test_delegate_ = delegate.get();
return delegate;
}
};
class TestPagePrintRequestHandler
: public enterprise_connectors::PagePrintRequestHandler {
public:
static std::unique_ptr<PagePrintRequestHandler> Create(
ContentAnalysisResponse::Result::TriggeredRule::Action action,
enterprise_connectors::ContentAnalysisInfo* content_analysis_info,
safe_browsing::BinaryUploadService* upload_service,
Profile* profile,
GURL url,
const std::string& printer_name,
const std::string& page_content_type,
base::ReadOnlySharedMemoryRegion page_region,
CompletionCallback callback) {
auto handler = base::WrapUnique(new TestPagePrintRequestHandler(
content_analysis_info, upload_service, profile, url, printer_name,
page_content_type, std::move(page_region), std::move(callback)));
handler->action_ = action;
return handler;
}
protected:
using PagePrintRequestHandler::PagePrintRequestHandler;
void UploadForDeepScanning(
std::unique_ptr<enterprise_connectors::PagePrintAnalysisRequest> request)
override {
ASSERT_EQ(request->printer_name(), kPrinterName);
OnContentAnalysisResponse(
safe_browsing::BinaryUploadService::Result::SUCCESS,
CreateResponse(action_));
}
private:
ContentAnalysisResponse::Result::TriggeredRule::Action action_;
};
class PrintContentAnalysisUtilsTest
: public PrintPreviewTest,
public testing::WithParamInterface<const char*> {
public:
PrintContentAnalysisUtilsTest() {
ContentAnalysisDelegate::DisableUIForTesting();
}
const char* policy_value() const { return GetParam(); }
void SetUp() override {
PrintPreviewTest::SetUp();
chrome::NewTab(browser());
SetDMTokenForTesting(policy::DMToken::CreateValidToken(kDmToken));
client_ = std::make_unique<policy::MockCloudPolicyClient>();
RealtimeReportingClientFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating([](content::BrowserContext* context) {
return std::unique_ptr<KeyedService>(
new enterprise_connectors::RealtimeReportingClient(context));
}));
RealtimeReportingClientFactory::GetForProfile(profile())
->SetBrowserCloudPolicyClientForTesting(client_.get());
identity_test_environment_.MakePrimaryAccountAvailable(
kUserName, signin::ConsentLevel::kSync);
RealtimeReportingClientFactory::GetForProfile(profile())
->SetIdentityManagerForTesting(
identity_test_environment_.identity_manager());
enterprise_connectors::test::SetAnalysisConnector(
profile()->GetPrefs(), enterprise_connectors::PRINT, policy_value());
enterprise_connectors::test::SetOnSecurityEventReporting(
profile()->GetPrefs(), true);
}
void TearDown() override {
RealtimeReportingClientFactory::GetForProfile(profile())
->SetBrowserCloudPolicyClientForTesting(nullptr);
SetDMTokenForTesting(policy::DMToken::CreateEmptyToken());
enterprise_connectors::PagePrintRequestHandler::ResetFactoryForTesting();
PrintPreviewTest::TearDown();
}
content::WebContents* contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<policy::MockCloudPolicyClient> client_;
signin::IdentityTestEnvironment identity_test_environment_;
#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
// This installs a fake SDK manager that creates fake SDK clients when
// its GetClient() method is called. This is needed so that calls to
// ContentAnalysisSdkManager::Get()->GetClient() do not fail.
enterprise_connectors::FakeContentAnalysisSdkManager sdk_manager_;
#endif
};
} // namespace
TEST_P(PrintContentAnalysisUtilsTest, GetPrintAnalysisData_BeforeSystemDialog) {
auto data = GetPrintAnalysisData(contents(),
PrintScanningContext::kBeforeSystemDialog);
ASSERT_TRUE(data);
ASSERT_EQ(data->settings.block_until_verdict,
enterprise_connectors::BlockUntilVerdict::kBlock);
ASSERT_EQ(data->reason,
enterprise_connectors::ContentAnalysisRequest::SYSTEM_DIALOG_PRINT);
ASSERT_EQ(policy_value() == kLocalPolicy,
data->settings.cloud_or_local_settings.is_local_analysis());
ASSERT_EQ(policy_value() == kCloudPolicy,
data->settings.cloud_or_local_settings.is_cloud_analysis());
}
TEST_P(PrintContentAnalysisUtilsTest,
GetPrintAnalysisData_SystemPrintAfterPreview) {
auto data = GetPrintAnalysisData(
contents(), PrintScanningContext::kSystemPrintAfterPreview);
// This enum value should never return a populated `data` since scanning
// should either take place before the system dialog with the
// `kBeforeSystemDialog` context, or right after it with the
// `kSystemPrintBeforePrintDocument` context.
ASSERT_FALSE(data);
}
TEST_P(PrintContentAnalysisUtilsTest,
GetPrintAnalysisData_NormalPrintAfterPreview) {
auto data = GetPrintAnalysisData(
contents(), PrintScanningContext::kNormalPrintAfterPreview);
ASSERT_TRUE(data);
ASSERT_EQ(data->settings.block_until_verdict,
enterprise_connectors::BlockUntilVerdict::kBlock);
ASSERT_EQ(data->reason,
enterprise_connectors::ContentAnalysisRequest::PRINT_PREVIEW_PRINT);
ASSERT_EQ(policy_value() == kLocalPolicy,
data->settings.cloud_or_local_settings.is_local_analysis());
ASSERT_EQ(policy_value() == kCloudPolicy,
data->settings.cloud_or_local_settings.is_cloud_analysis());
}
TEST_P(PrintContentAnalysisUtilsTest,
GetPrintAnalysisData_NormalPrintBeforePrintDocument) {
auto data = GetPrintAnalysisData(
contents(), PrintScanningContext::kNormalPrintBeforePrintDocument);
// This enum value should never return a populated `data` since scanning
// should either take place before the preview dialog with the
// `kBeforePreview` context, or right after it with the
// `kNormalPrintAfterPreview` context.
ASSERT_FALSE(data);
}
TEST_P(PrintContentAnalysisUtilsTest,
GetPrintAnalysisData_SystemPrintBeforePrintDocument) {
auto data = GetPrintAnalysisData(
contents(), PrintScanningContext::kSystemPrintBeforePrintDocument);
ASSERT_TRUE(data);
ASSERT_EQ(data->settings.block_until_verdict,
enterprise_connectors::BlockUntilVerdict::kBlock);
ASSERT_EQ(data->reason,
enterprise_connectors::ContentAnalysisRequest::SYSTEM_DIALOG_PRINT);
ASSERT_EQ(policy_value() == kLocalPolicy,
data->settings.cloud_or_local_settings.is_local_analysis());
ASSERT_EQ(policy_value() == kCloudPolicy,
data->settings.cloud_or_local_settings.is_cloud_analysis());
}
#if BUILDFLAG(IS_MAC)
TEST_P(PrintContentAnalysisUtilsTest,
GetPrintAnalysisData_MacOpenPdfInPreview) {
auto data =
GetPrintAnalysisData(contents(), PrintScanningContext::kOpenPdfInPreview);
ASSERT_TRUE(data);
ASSERT_EQ(data->settings.block_until_verdict,
enterprise_connectors::BlockUntilVerdict::kBlock);
ASSERT_EQ(data->reason,
enterprise_connectors::ContentAnalysisRequest::PRINT_PREVIEW_PRINT);
ASSERT_EQ(policy_value() == kLocalPolicy,
data->settings.cloud_or_local_settings.is_local_analysis());
ASSERT_EQ(policy_value() == kCloudPolicy,
data->settings.cloud_or_local_settings.is_cloud_analysis());
}
#endif // BUILDFLAG(IS_MAC)
TEST_P(PrintContentAnalysisUtilsTest, PrintIfAllowedByPolicyAllowed) {
ContentAnalysisDelegate::SetFactoryForTesting(
base::BindRepeating(&PrintTestContentAnalysisDelegate::Create));
enterprise_connectors::PagePrintRequestHandler::SetFactoryForTesting(
base::BindRepeating(
&TestPagePrintRequestHandler::Create,
ContentAnalysisResponse::Result::TriggeredRule::ACTION_UNSPECIFIED));
enterprise_connectors::test::EventReportValidator validator(client_.get());
validator.ExpectNoReport();
auto data = CreateData();
base::RunLoop run_loop;
auto on_verdict = base::BindLambdaForTesting([&run_loop](bool allowed) {
ASSERT_TRUE(allowed);
run_loop.Quit();
});
PrintIfAllowedByPolicy(data, contents(), kPrinterName,
PrintScanningContext::kNormalPrintAfterPreview,
std::move(on_verdict),
/*hide_preview=*/base::DoNothing());
run_loop.Run();
}
TEST_P(PrintContentAnalysisUtilsTest, PrintIfAllowedByPolicyReportOnly) {
ContentAnalysisDelegate::SetFactoryForTesting(
base::BindRepeating(&PrintTestContentAnalysisDelegate::Create));
enterprise_connectors::PagePrintRequestHandler::SetFactoryForTesting(
base::BindRepeating(
&TestPagePrintRequestHandler::Create,
ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY));
enterprise_connectors::test::EventReportValidator validator(client_.get());
base::RunLoop validator_run_loop;
validator.SetDoneClosure(validator_run_loop.QuitClosure());
validator.ExpectSensitiveDataEvent(
/*url*/ "",
/*tab_url*/ "",
/*source*/ "",
/*destination*/ kPrinterName,
/*filename*/ "New Tab",
/*sha*/ "",
/*trigger*/
enterprise_connectors::kPagePrintDataTransferEventTrigger,
/*dlp_verdict*/
CreateResult(ContentAnalysisResponse::Result::TriggeredRule::REPORT_ONLY),
/*mimetype*/ PrintMimeTypes(),
/*size*/ std::nullopt,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::ALLOWED),
/*username*/ kUserName,
/*profile_identifier*/ profile()->GetPath().AsUTF8Unsafe(),
/*scan_id*/ kScanId,
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
auto data = CreateData();
base::RunLoop run_loop;
auto on_verdict = base::BindLambdaForTesting([&run_loop](bool allowed) {
ASSERT_TRUE(allowed);
run_loop.Quit();
});
PrintIfAllowedByPolicy(data, contents(), kPrinterName,
PrintScanningContext::kNormalPrintAfterPreview,
std::move(on_verdict),
/*hide_preview=*/base::DoNothing());
run_loop.Run();
validator_run_loop.Run();
}
TEST_P(PrintContentAnalysisUtilsTest, PrintIfAllowedByPolicyWarnThenCancel) {
ContentAnalysisDelegate::SetFactoryForTesting(
base::BindRepeating(&PrintTestContentAnalysisDelegate::Create));
enterprise_connectors::PagePrintRequestHandler::SetFactoryForTesting(
base::BindRepeating(
&TestPagePrintRequestHandler::Create,
ContentAnalysisResponse::Result::TriggeredRule::WARN));
// The cancel callback is called after the verdict is received.
enterprise_connectors::ContentAnalysisDelegate::
SetOnAckAllRequestsCallbackForTesting(base::BindOnce(
[](const std::map<std::string,
enterprise_connectors::
ContentAnalysisAcknowledgement::FinalAction>&
map) {
ASSERT_TRUE(test_delegate_);
test_delegate_->Cancel(/* warning= */ true);
}));
enterprise_connectors::test::EventReportValidator validator(client_.get());
base::RunLoop validator_run_loop;
validator.SetDoneClosure(validator_run_loop.QuitClosure());
validator.ExpectSensitiveDataEvent(
/*url*/ "",
/*tab_url*/ "",
/*source*/ "",
/*destination*/ kPrinterName,
/*filename*/ "New Tab",
/*sha*/ "",
/*trigger*/
enterprise_connectors::kPagePrintDataTransferEventTrigger,
/*dlp_verdict*/
CreateResult(ContentAnalysisResponse::Result::TriggeredRule::WARN),
/*mimetype*/ PrintMimeTypes(),
/*size*/ std::nullopt,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::WARNED),
/*username*/ kUserName,
/*profile_identifier*/ profile()->GetPath().AsUTF8Unsafe(),
/*scan_id*/ kScanId,
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
auto data = CreateData();
base::RunLoop run_loop;
auto on_verdict = base::BindLambdaForTesting([&run_loop](bool allowed) {
ASSERT_FALSE(allowed);
run_loop.Quit();
});
PrintIfAllowedByPolicy(data, contents(), kPrinterName,
PrintScanningContext::kNormalPrintAfterPreview,
std::move(on_verdict),
/*hide_preview=*/base::DoNothing());
run_loop.Run();
validator_run_loop.Run();
}
// TODO(http://crbug.com/434099738): Flaky on multiple platforms.
TEST_P(PrintContentAnalysisUtilsTest,
DISABLED_PrintIfAllowedByPolicyWarnedThenBypass) {
ContentAnalysisDelegate::SetFactoryForTesting(
base::BindRepeating(&PrintTestContentAnalysisDelegate::Create));
enterprise_connectors::PagePrintRequestHandler::SetFactoryForTesting(
base::BindRepeating(
&TestPagePrintRequestHandler::Create,
ContentAnalysisResponse::Result::TriggeredRule::WARN));
// The bypass callback is called after the verdict is received.
enterprise_connectors::ContentAnalysisDelegate::
SetOnAckAllRequestsCallbackForTesting(base::BindOnce(
[](const std::map<std::string,
enterprise_connectors::
ContentAnalysisAcknowledgement::FinalAction>&
map) {
ASSERT_TRUE(test_delegate_);
test_delegate_->SetPageWarningForTesting();
test_delegate_->BypassWarnings(kUserJustification);
}));
base::RunLoop validator_warn_run_loop;
enterprise_connectors::test::EventReportValidator validator(client_.get());
validator.SetDoneClosure(validator_warn_run_loop.QuitClosure());
validator.ExpectSensitiveDataEventWarnThenBypass(
/*url*/ "",
/*tab_url*/ "",
/*source*/ "",
/*destination*/ kPrinterName,
/*filename*/ "New Tab",
/*sha*/ "",
/*trigger*/
enterprise_connectors::kPagePrintDataTransferEventTrigger,
/*dlp_verdict*/
CreateResult(ContentAnalysisResponse::Result::TriggeredRule::WARN),
/*mimetype*/ PrintMimeTypes(),
/*size*/ std::nullopt,
/*username*/ kUserName,
/*profile_identifier*/ profile()->GetPath().AsUTF8Unsafe(),
/*scan_id*/ kScanId,
/*content_transfer_method*/ std::nullopt,
/*user_justifications*/ {std::nullopt, kUserJustification});
auto data = CreateData();
base::RunLoop run_loop;
auto on_verdict = base::BindLambdaForTesting([&run_loop](bool allowed) {
ASSERT_TRUE(allowed);
run_loop.Quit();
});
PrintIfAllowedByPolicy(data, contents(), kPrinterName,
PrintScanningContext::kNormalPrintAfterPreview,
std::move(on_verdict),
/*hide_preview=*/base::DoNothing());
run_loop.Run();
validator_warn_run_loop.Run();
}
TEST_P(PrintContentAnalysisUtilsTest, PrintIfAllowedByPolicyBlocked) {
ContentAnalysisDelegate::SetFactoryForTesting(
base::BindRepeating(&PrintTestContentAnalysisDelegate::Create));
enterprise_connectors::PagePrintRequestHandler::SetFactoryForTesting(
base::BindRepeating(
&TestPagePrintRequestHandler::Create,
ContentAnalysisResponse::Result::TriggeredRule::BLOCK));
enterprise_connectors::test::EventReportValidator validator(client_.get());
base::RunLoop validator_run_loop;
validator.SetDoneClosure(validator_run_loop.QuitClosure());
validator.ExpectSensitiveDataEvent(
/*url*/ "",
/*tab_url*/ "",
/*source*/ "",
/*destination*/ kPrinterName,
/*filename*/ "New Tab",
/*sha*/ "",
/*trigger*/
enterprise_connectors::kPagePrintDataTransferEventTrigger,
/*dlp_verdict*/
CreateResult(ContentAnalysisResponse::Result::TriggeredRule::BLOCK),
/*mimetype*/ PrintMimeTypes(),
/*size*/ std::nullopt,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::BLOCKED),
/*username*/ kUserName,
/*profile_identifier*/ profile()->GetPath().AsUTF8Unsafe(),
/*scan_id*/ kScanId,
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
auto data = CreateData();
base::RunLoop run_loop;
auto on_verdict = base::BindLambdaForTesting([&run_loop](bool allowed) {
ASSERT_FALSE(allowed);
run_loop.Quit();
});
PrintIfAllowedByPolicy(data, contents(), kPrinterName,
PrintScanningContext::kNormalPrintAfterPreview,
std::move(on_verdict),
/*hide_preview=*/base::DoNothing());
validator_run_loop.Run();
run_loop.Run();
}
TEST_P(PrintContentAnalysisUtilsTest, PrintIfAllowedByPolicyNullInitiator) {
enterprise_connectors::test::EventReportValidator validator(client_.get());
validator.ExpectNoReport();
auto data = CreateData();
base::RunLoop run_loop;
auto on_verdict = base::BindLambdaForTesting([&run_loop](bool allowed) {
EXPECT_FALSE(allowed);
run_loop.Quit();
});
PrintIfAllowedByPolicy(data, /*initiator=*/nullptr, kPrinterName,
PrintScanningContext::kNormalPrintAfterPreview,
std::move(on_verdict),
/*hide_preview=*/base::DoNothing());
run_loop.Run();
}
INSTANTIATE_TEST_SUITE_P(
All,
PrintContentAnalysisUtilsTest,
/*policy_value=*/testing::Values(kLocalPolicy, kCloudPolicy));
} // namespace enterprise_data_protection