blob: efb4dc65242ccbe3e1b28888a2bb805eeed6ffa5 [file] [log] [blame]
// 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 <memory>
#include <optional>
#include "base/base64.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_commands.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/download/download_item_model.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/enterprise/connectors/connectors_service.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_browsertest_base.h"
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"
#include "chrome/browser/enterprise/identifiers/profile_id_service_factory.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h"
#include "chrome/browser/safe_browsing/download_protection/deep_scanning_request.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/file_system_access/file_system_access_test_utils.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_features.h"
#include "components/enterprise/browser/identifiers/profile_id_service.h"
#include "components/enterprise/common/proto/connectors.pb.h"
#include "components/enterprise/connectors/core/reporting_constants.h"
#include "components/enterprise/obfuscation/core/utils.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/content/common/file_type_policies_test_util.h"
#include "components/safe_browsing/core/browser/db/test_database_manager.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/save_page_type.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/mojom/data_pipe_getter.mojom.h"
#include "services/network/test/test_utils.h"
namespace safe_browsing {
namespace {
constexpr char kUserName[] = "test@chromium.org";
constexpr char kResumableUploadUrl[] =
"http://uploads.google.com?upload_id=ABC&upload_protocol=resumable";
constexpr int64_t kSingleChunkObfuscationOverhead =
enterprise_obfuscation::kKeySize + enterprise_obfuscation::kNonceSize +
enterprise_obfuscation::kAuthTagSize;
constexpr char kTestContent[] = "test content";
// Extract the metadata proto from the raw request string based on multipart
// upload protocol. Returns true on success.
bool GetMultipartUploadMetadata(
const std::string& upload_request,
enterprise_connectors::ContentAnalysisRequest* out_proto) {
// The request is of the following format, see multipart_uploader.h for
// details:
// ---MultipartBoundary---
// <Headers for metadata>
//
// <Base64-encoded metadata>
// ---MultipartBoundary---
// <Headers for uploaded data>
//
// <Uploaded data>
// ---MultipartBoundary---
size_t boundary_end = upload_request.find("\r\n");
std::string multipart_boundary = upload_request.substr(0, boundary_end);
size_t headers_end = upload_request.find("\r\n\r\n");
size_t metadata_end =
upload_request.find("\r\n" + multipart_boundary, headers_end);
std::string encoded_metadata =
upload_request.substr(headers_end + 4, metadata_end - headers_end - 4);
std::string serialized_metadata;
base::Base64Decode(encoded_metadata, &serialized_metadata);
return out_proto->ParseFromString(serialized_metadata);
}
// Extract the metadata proto from the raw request string based on resumable
// upload protocol. Returns true on success.
bool GetResumableUploadMetadata(
const std::string& upload_request,
enterprise_connectors::ContentAnalysisRequest* out_proto) {
// The request content is of the following format, see resumable_uploader.h
// for details: "metadata\r\n"
size_t boundary_end = upload_request.find("\r\n");
std::string encoded_metadata = upload_request.substr(0, boundary_end);
std::string serialized_metadata;
base::Base64Decode(encoded_metadata, &serialized_metadata);
return out_proto->ParseFromString(serialized_metadata);
}
} // namespace
// Integration tests for download deep scanning behavior, only mocking network
// traffic.
class DownloadDeepScanningBrowserTestBase
: public enterprise_connectors::test::DeepScanningBrowserTestBase,
public content::DownloadManager::Observer,
public download::DownloadItem::Observer {
public:
// |connectors_machine_scope| indicates whether the Connector prefs such as
// OnFileDownloadedEnterpriseConnector and OnSecurityEventEnterpriseConnector
// should be set at the machine or user scope.
// |is_consumer| indicates whether the content scan is a consumer or an
// enterprise scan.
// |is_obfuscated| indicates whether the downloaded file has been obfuscated
// to prevent user access. Currently, this is done while waiting for an
// enterprise deep scan verdict.
explicit DownloadDeepScanningBrowserTestBase(bool connectors_machine_scope,
bool is_consumer,
bool is_obfuscated)
: is_consumer_(is_consumer),
is_obfuscated_(is_obfuscated),
connectors_machine_scope_(connectors_machine_scope) {
is_obfuscated_ ? enabled_features_.push_back(
enterprise_obfuscation::kEnterpriseFileObfuscation)
: disabled_features_.push_back(
enterprise_obfuscation::kEnterpriseFileObfuscation);
}
void OnDownloadCreated(content::DownloadManager* manager,
download::DownloadItem* item) override {
item->AddObserver(this);
download_items_.insert(item);
}
void OnDownloadDestroyed(download::DownloadItem* item) override {
download_items_.erase(item);
}
void SetUpReporting() {
enterprise_connectors::test::SetOnSecurityEventReporting(
browser()->profile()->GetPrefs(),
/*enabled*/ true, /*enabled_event_names*/ {},
/*enabled_opt_in_events*/ {}, connectors_machine_scope());
client_ = std::make_unique<policy::MockCloudPolicyClient>();
client_->SetDMToken("dm_token");
#if BUILDFLAG(IS_CHROMEOS)
enterprise_connectors::RealtimeReportingClientFactory::GetForProfile(
browser()->profile())
->SetBrowserCloudPolicyClientForTesting(client_.get());
#else
if (connectors_machine_scope()) {
enterprise_connectors::RealtimeReportingClientFactory::GetForProfile(
browser()->profile())
->SetBrowserCloudPolicyClientForTesting(client_.get());
} else {
enterprise_connectors::RealtimeReportingClientFactory::GetForProfile(
browser()->profile())
->SetProfileCloudPolicyClientForTesting(client_.get());
}
#endif
identity_test_environment_ =
std::make_unique<signin::IdentityTestEnvironment>();
identity_test_environment_->MakePrimaryAccountAvailable(
kUserName, signin::ConsentLevel::kSync);
enterprise_connectors::RealtimeReportingClientFactory::GetForProfile(
browser()->profile())
->SetIdentityManagerForTesting(
identity_test_environment_->identity_manager());
}
policy::MockCloudPolicyClient* client() { return client_.get(); }
protected:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(std::move(enabled_features_),
std::move(disabled_features_));
test_sb_factory_ = std::make_unique<TestSafeBrowsingServiceFactory>();
test_sb_factory_->UseV4LocalDatabaseManager();
SafeBrowsingService::RegisterFactory(test_sb_factory_.get());
InProcessBrowserTest::SetUp();
}
void TearDown() override {
InProcessBrowserTest::TearDown();
SafeBrowsingService::RegisterFactory(nullptr);
}
void SetUpOnMainThread() override {
embedded_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
ASSERT_TRUE(embedded_test_server()->Start());
SetBinaryUploadServiceTestFactory();
SetUrlLoaderInterceptor();
ObserveDownloadManager();
if (!is_consumer_) {
AuthorizeForDeepScanning();
#if BUILDFLAG(IS_CHROMEOS)
SetDMTokenForTesting(policy::DMToken::CreateValidToken("dm_token"));
#else
if (connectors_machine_scope()) {
SetDMTokenForTesting(policy::DMToken::CreateValidToken("dm_token"));
} else {
enterprise_connectors::test::SetProfileDMToken(browser()->profile(),
"dm_token");
}
#endif
enterprise_connectors::test::SetAnalysisConnector(
browser()->profile()->GetPrefs(),
enterprise_connectors::FILE_DOWNLOADED,
R"({
"service_provider": "google",
"enable": [
{
"url_list": ["*"],
"tags": ["dlp", "malware"]
}
],
"block_until_verdict": 1,
"block_password_protected": true
})",
connectors_machine_scope());
}
}
void WaitForDownloadToFinish() {
content::DownloadManager* download_manager =
browser()->profile()->GetDownloadManager();
content::DownloadTestObserverTerminal observer(
download_manager, 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_QUIT);
observer.WaitForFinished();
}
void WaitForDeepScanRequest() {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
waiting_for_upload_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
void WaitForMetadataCheck() {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
waiting_for_metadata_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
void ExpectMetadataResponse(const ClientDownloadResponse& response) {
test_sb_factory_->test_safe_browsing_service()
->GetTestUrlLoaderFactory()
->AddResponse(test_sb_factory_->test_safe_browsing_service()
->download_protection_service()
->GetDownloadRequestUrl()
.spec(),
response.SerializeAsString());
}
void PrepareConnectorUrl(const std::vector<std::string>& tags) {
if (is_consumer_) {
connector_url_ =
"https://safebrowsing.google.com/safebrowsing/uploads/consumer";
} else {
connector_url_ =
"https://safebrowsing.google.com/safebrowsing/uploads/"
"scan?device_token=dm_token&connector=OnFileDownloaded";
for (const std::string& tag : tags) {
connector_url_ += ("&tag=" + tag);
}
}
}
void ExpectContentAnalysisResumableMetadataResponse(
const std::vector<std::string>& tags) {
PrepareConnectorUrl(tags);
auto metadata_response_head = network::CreateURLResponseHead(net::HTTP_OK);
metadata_response_head->headers->AddHeader("X-Goog-Upload-Status",
"active");
metadata_response_head->headers->AddHeader("X-Goog-Upload-URL",
kResumableUploadUrl);
test_sb_factory_->test_safe_browsing_service()
->GetTestUrlLoaderFactory()
->AddResponse(GURL(connector_url_), std::move(metadata_response_head),
"metadata_response",
network::URLLoaderCompletionStatus(net::OK));
}
void ExpectContentAnalysisResumableContentResponse(
const enterprise_connectors::ContentAnalysisResponse& response) {
auto content_response_head = network::CreateURLResponseHead(net::HTTP_OK);
content_response_head->headers->AddHeader("X-Goog-Upload-Status", "final");
test_sb_factory_->test_safe_browsing_service()
->GetTestUrlLoaderFactory()
->AddResponse(GURL(kResumableUploadUrl),
std::move(content_response_head),
response.SerializeAsString(),
network::URLLoaderCompletionStatus(net::OK));
}
void ExpectContentAnalysisMultipartResponse(
const enterprise_connectors::ContentAnalysisResponse& response,
const std::vector<std::string>& tags) {
PrepareConnectorUrl(tags);
test_sb_factory_->test_safe_browsing_service()
->GetTestUrlLoaderFactory()
->AddResponse(connector_url_, response.SerializeAsString());
}
void ExpectContentAnalysisUploadFailure(
net::HttpStatusCode status_code,
const std::vector<std::string>& tags) {
PrepareConnectorUrl(tags);
test_sb_factory_->test_safe_browsing_service()
->GetTestUrlLoaderFactory()
->AddResponse(connector_url_, "", status_code);
}
base::FilePath GetTestDataDirectory() {
base::FilePath test_file_directory;
base::PathService::Get(chrome::DIR_TEST_DATA, &test_file_directory);
return test_file_directory;
}
TestSafeBrowsingServiceFactory* test_sb_factory() {
return test_sb_factory_.get();
}
const enterprise_connectors::ContentAnalysisRequest& last_request() const {
return last_request_;
}
const base::flat_set<raw_ptr<download::DownloadItem, CtnExperimental>>&
download_items() {
return download_items_;
}
void SetBinaryUploadServiceTestFactory() {
CloudBinaryUploadServiceFactory::GetInstance()->SetTestingFactory(
browser()->profile(),
base::BindRepeating(
&DownloadDeepScanningBrowserTestBase::CreateBinaryUploadService,
base::Unretained(this)));
}
void ObserveDownloadManager() {
content::DownloadManager* download_manager =
browser()->profile()->GetDownloadManager();
download_manager->AddObserver(this);
}
void SetUrlLoaderInterceptor() {
test_sb_factory()->test_safe_browsing_service()->SetUseTestUrlLoaderFactory(
true);
test_sb_factory()
->test_safe_browsing_service()
->GetTestUrlLoaderFactory()
->SetInterceptor(base::BindRepeating(
&DownloadDeepScanningBrowserTestBase::InterceptRequest,
base::Unretained(this)));
}
void AuthorizeForDeepScanning() {
static_cast<safe_browsing::CloudBinaryUploadService*>(
CloudBinaryUploadServiceFactory::GetForProfile(browser()->profile()))
->SetAuthForTesting(
"dm_token",
/*auth_check_result=*/BinaryUploadService::Result::SUCCESS);
}
bool connectors_machine_scope() const { return connectors_machine_scope_; }
bool is_obfuscated() const { return is_obfuscated_; }
std::string GetProfileIdentifier() const {
#if BUILDFLAG(IS_CHROMEOS)
return browser()->profile()->GetPath().AsUTF8Unsafe();
#else
if (connectors_machine_scope_) {
return browser()->profile()->GetPath().AsUTF8Unsafe();
}
auto* profile_id_service =
enterprise::ProfileIdServiceFactory::GetForProfile(
browser()->profile());
if (profile_id_service && profile_id_service->GetProfileId().has_value()) {
return profile_id_service->GetProfileId().value();
}
return std::string();
#endif
}
std::vector<base::test::FeatureRef> enabled_features_;
std::vector<base::test::FeatureRef> disabled_features_;
private:
std::unique_ptr<KeyedService> CreateBinaryUploadService(
content::BrowserContext* browser_context) {
Profile* profile = Profile::FromBrowserContext(browser_context);
return std::make_unique<safe_browsing::CloudBinaryUploadService>(
g_browser_process->safe_browsing_service()->GetURLLoaderFactory(
profile),
profile);
}
std::string GetDataPipeUploadData(const network::ResourceRequest& request) {
EXPECT_TRUE(request.request_body);
EXPECT_EQ(1u, request.request_body->elements()->size());
network::DataElement& data_pipe_element =
(*request.request_body->elements_mutable())[0];
data_pipe_getter_.reset();
data_pipe_getter_.Bind(data_pipe_element.As<network::DataElementDataPipe>()
.ReleaseDataPipeGetter());
EXPECT_TRUE(data_pipe_getter_);
mojo::ScopedDataPipeProducerHandle data_pipe_producer;
mojo::ScopedDataPipeConsumerHandle data_pipe_consumer;
base::RunLoop run_loop;
EXPECT_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(nullptr, data_pipe_producer,
data_pipe_consumer));
data_pipe_getter_->Read(
std::move(data_pipe_producer),
base::BindLambdaForTesting([&run_loop](int32_t status, uint64_t size) {
EXPECT_EQ(net::OK, status);
run_loop.Quit();
}));
data_pipe_getter_.FlushForTesting();
run_loop.Run();
EXPECT_TRUE(data_pipe_consumer.is_valid());
std::string body;
while (true) {
std::string buffer(1024, '\0');
size_t actually_read_bytes = 0;
MojoResult result = data_pipe_consumer->ReadData(
MOJO_READ_DATA_FLAG_NONE, base::as_writable_byte_span(buffer),
actually_read_bytes);
if (result == MOJO_RESULT_SHOULD_WAIT) {
base::RunLoop().RunUntilIdle();
continue;
}
if (result != MOJO_RESULT_OK) {
break;
}
body.append(std::string_view(buffer).substr(0, actually_read_bytes));
}
return body;
}
bool MaybeHandleDownloadRequest(const network::ResourceRequest& request) {
if (request.url != test_sb_factory_->test_safe_browsing_service()
->download_protection_service()
->GetDownloadRequestUrl()) {
return false;
}
if (waiting_for_metadata_closure_) {
std::move(waiting_for_metadata_closure_).Run();
}
return true;
}
void HandleConsumerRequest(const network::ResourceRequest& request) {
if (request.url == safe_browsing::CloudBinaryUploadService::GetUploadUrl(
/*is_consumer_scan_eligible=*/true)) {
ASSERT_TRUE(GetMultipartUploadMetadata(GetDataPipeUploadData(request),
&last_request_));
if (waiting_for_upload_closure_) {
std::move(waiting_for_upload_closure_).Run();
}
}
}
void HandleEnterpriseRequest(const network::ResourceRequest& request) {
if (request.url == GURL(kResumableUploadUrl)) {
GetDataPipeUploadData(request);
if (waiting_for_upload_closure_) {
std::move(waiting_for_upload_closure_).Run();
}
return;
}
if (request.url == safe_browsing::CloudBinaryUploadService::GetUploadUrl(
/*is_consumer_scan_eligible=*/false)) {
ASSERT_TRUE(GetMultipartUploadMetadata(GetDataPipeUploadData(request),
&last_request_));
if (waiting_for_upload_closure_) {
std::move(waiting_for_upload_closure_).Run();
}
return;
}
if (request.url == connector_url_) {
ASSERT_TRUE(GetResumableUploadMetadata(network::GetUploadData(request),
&last_request_));
}
}
void InterceptRequest(const network::ResourceRequest& request) {
if (MaybeHandleDownloadRequest(request)) {
return;
}
if (is_consumer_) {
HandleConsumerRequest(request);
return;
}
HandleEnterpriseRequest(request);
}
base::test::ScopedFeatureList scoped_feature_list_;
bool is_consumer_;
bool is_obfuscated_;
std::unique_ptr<TestSafeBrowsingServiceFactory> test_sb_factory_;
enterprise_connectors::ContentAnalysisRequest last_request_;
std::string connector_url_;
base::OnceClosure waiting_for_upload_closure_;
base::OnceClosure waiting_for_metadata_closure_;
base::flat_set<raw_ptr<download::DownloadItem, CtnExperimental>>
download_items_;
std::unique_ptr<policy::MockCloudPolicyClient> client_;
std::unique_ptr<signin::IdentityTestEnvironment> identity_test_environment_;
bool connectors_machine_scope_;
mojo::Remote<network::mojom::DataPipeGetter> data_pipe_getter_;
};
class ConsumerDeepScanningBrowserTest
: public DownloadDeepScanningBrowserTestBase {
public:
ConsumerDeepScanningBrowserTest()
: DownloadDeepScanningBrowserTestBase(/*connectors_machine_scope=*/true,
/*is_consumer=*/true,
/*is_obfuscated=*/false) {}
};
IN_PROC_BROWSER_TEST_F(ConsumerDeepScanningBrowserTest, ErrorIndicatesFailure) {
SetSafeBrowsingState(browser()->profile()->GetPrefs(),
SafeBrowsingState::ENHANCED_PROTECTION);
ClientDownloadResponse metadata_response;
metadata_response.set_request_deep_scan(true);
ExpectMetadataResponse(metadata_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ExpectContentAnalysisUploadFailure(net::HTTP_INTERNAL_SERVER_ERROR, {});
WaitForDownloadToFinish();
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* download = *download_items().begin();
EXPECT_EQ(download->GetState(), download::DownloadItem::COMPLETE);
EXPECT_EQ(
download->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_FAILED);
}
class DownloadDeepScanningBrowserTest
: public DownloadDeepScanningBrowserTestBase,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
DownloadDeepScanningBrowserTest()
: DownloadDeepScanningBrowserTestBase(
/*connectors_machine_scope=*/std::get<0>(GetParam()),
/*is_consumer=*/false,
/*is_obfuscated=*/std::get<1>(GetParam())) {}
};
INSTANTIATE_TEST_SUITE_P(,
DownloadDeepScanningBrowserTest,
testing::Combine(
/*connectors_machine_scope=*/testing::Bool(),
/*is_obfuscated=*/testing::Bool()));
IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest,
SafeDownloadHasCorrectDangerType) {
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
// The file is SAFE according to the metadata check
ClientDownloadResponse metadata_response;
metadata_response.set_verdict(ClientDownloadResponse::SAFE);
ExpectMetadataResponse(metadata_response);
// The DLP scan runs synchronously, but doesn't find anything.
enterprise_connectors::ContentAnalysisResponse sync_response;
auto* dlp_result = sync_response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
// The malware scan finishes synchronously, and doesn't find anything.
auto* malware_result = sync_response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(sync_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
WaitForDownloadToFinish();
// The file should be deep scanned, and safe.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(
item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE);
EXPECT_EQ(item->GetState(), download::DownloadItem::COMPLETE);
}
IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest, FailedScanFailsOpen) {
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
// The file is SAFE according to the metadata check
ClientDownloadResponse metadata_response;
metadata_response.set_verdict(ClientDownloadResponse::SAFE);
ExpectMetadataResponse(metadata_response);
// The DLP scan runs synchronously, but doesn't find anything.
enterprise_connectors::ContentAnalysisResponse sync_response;
auto* dlp_result = sync_response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
// The malware scan finishes synchronously, and fails.
auto* malware_result = sync_response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(sync_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
WaitForDownloadToFinish();
// The file should be safe, but not deep scanned.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
EXPECT_EQ(item->GetState(), download::DownloadItem::COMPLETE);
}
IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest,
PartialFailureShowsMalwareWarning) {
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
// The file is SAFE according to the metadata check
ClientDownloadResponse metadata_response;
metadata_response.set_verdict(ClientDownloadResponse::SAFE);
ExpectMetadataResponse(metadata_response);
// The DLP scan runs synchronously, and fails.
enterprise_connectors::ContentAnalysisResponse sync_response;
auto* dlp_result = sync_response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
// The malware scan finishes synchronously, and finds malware.
auto* malware_result = sync_response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* malware_rule = malware_result->add_triggered_rules();
malware_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
malware_rule->set_rule_name("malware");
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(sync_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
WaitForDownloadToFinish();
// The file should be dangerous.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(
item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT);
EXPECT_EQ(item->GetState(), download::DownloadItem::IN_PROGRESS);
}
IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest,
PartialFailureShowsDlpWarning) {
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
// The file is SAFE according to the metadata check
ClientDownloadResponse metadata_response;
metadata_response.set_verdict(ClientDownloadResponse::SAFE);
ExpectMetadataResponse(metadata_response);
// The DLP scan runs synchronously, and finds a violation.
enterprise_connectors::ContentAnalysisResponse sync_response;
auto* dlp_result = sync_response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_rule = dlp_result->add_triggered_rules();
dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
// The malware scan finishes synchronously, and fails.
auto* malware_result = sync_response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(sync_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
WaitForDownloadToFinish();
// The file should be blocked for sensitive content.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::
DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK);
EXPECT_EQ(item->GetState(), download::DownloadItem::INTERRUPTED);
}
// Regardless of resumable or multipart upload protocol, when a file is password
// protected and the `block_password_protected` setting is on, the file should
// be blocked.
IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest,
PasswordProtectedTxtFilesAreBlocked) {
// TODO(crbug.com/378490429): Add support for obfuscated password protected
// files.
if (is_obfuscated()) {
GTEST_SKIP() << "Encryption status cannot be detected in obfuscated files.";
}
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
// The file is SAFE according to the metadata check
ClientDownloadResponse metadata_response;
metadata_response.set_verdict(ClientDownloadResponse::SAFE);
ExpectMetadataResponse(metadata_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/encrypted_txt.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
WaitForDownloadToFinish();
// The file should be blocked for containing a password protected file.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::
DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED);
EXPECT_EQ(item->GetState(), download::DownloadItem::INTERRUPTED);
}
// TODO(https://crbug.com/414822762): Reenable the test once the flakiness is
// fixed.
IN_PROC_BROWSER_TEST_P(DownloadDeepScanningBrowserTest,
DISABLED_DlpAndMalwareViolations) {
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
SetUpReporting();
base::HistogramTester histograms;
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// The DLP scan finishes synchronously and find a violation. Malware scanning
// finds violation.
enterprise_connectors::ContentAnalysisResponse sync_response;
auto* dlp_result = sync_response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_rule = dlp_result->add_triggered_rules();
dlp_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
dlp_rule->set_rule_name("dlp_rule_name");
auto* malware_result = sync_response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* malware_rule = malware_result->add_triggered_rules();
malware_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
malware_rule->set_rule_name("uws");
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(sync_response);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
// Both the DLP and malware violations generate an event.
base::RunLoop run_loop;
std::set<std::string> zip_types = {"application/zip",
"application/x-zip-compressed"};
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(run_loop.QuitClosure());
validator.ExpectSensitiveDataEventAndDangerousDeepScanningResult(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/
connectors_machine_scope()
? (*download_items().begin())->GetTargetFilePath().AsUTF8Unsafe()
: "zipfile_two_archives.zip",
// sha256sum chrome/test/data/safe_browsing/download_protection/\
// zipfile_two_archives.zip | tr '[:lower:]' '[:upper:]'
/*sha*/
"339C8FFDAE735C4F1846D0E6FF07FBD85CAEE6D96045AAEF5B30F3220836643C",
/*threat_type*/ "POTENTIALLY_UNWANTED",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ *dlp_result,
/*mimetypes*/ &zip_types,
/*size*/ is_obfuscated() ? 276 + kSingleChunkObfuscationOverhead : 276,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::WARNED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token());
WaitForDownloadToFinish();
// The file should be blocked.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(
item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED);
EXPECT_EQ(item->GetState(), download::DownloadItem::IN_PROGRESS);
// UMAs for this request should only be recorded once.
histograms.ExpectUniqueSample("SafeBrowsingBinaryUploadRequest.Result",
BinaryUploadService::Result::SUCCESS, 1);
histograms.ExpectUniqueSample("SafeBrowsingBinaryUploadRequest.DlpResult",
true, 1);
histograms.ExpectUniqueSample("SafeBrowsingBinaryUploadRequest.MalwareResult",
true, 1);
run_loop.Run();
}
class DownloadRestrictionsDeepScanningBrowserTest
: public DownloadDeepScanningBrowserTestBase,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
DownloadRestrictionsDeepScanningBrowserTest()
: DownloadDeepScanningBrowserTestBase(
/*connectors_machine_scope=*/std::get<0>(GetParam()),
/*is_consumer=*/false,
/*is_obfuscated=*/false) {
use_proto_format() ? enabled_features_.push_back(
policy::kUploadRealtimeReportingEventsUsingProto)
: disabled_features_.push_back(
policy::kUploadRealtimeReportingEventsUsingProto);
}
~DownloadRestrictionsDeepScanningBrowserTest() override = default;
void SetUpOnMainThread() override {
DownloadDeepScanningBrowserTestBase::SetUpOnMainThread();
browser()->profile()->GetPrefs()->SetInteger(
policy::policy_prefs::kDownloadRestrictions,
static_cast<int>(policy::DownloadRestriction::DANGEROUS_FILES));
enterprise_connectors::test::SetAnalysisConnector(
browser()->profile()->GetPrefs(),
enterprise_connectors::FILE_DOWNLOADED,
R"({
"service_provider": "google",
"enable": [
{
"url_list": ["*"],
"tags": ["malware"]
}
],
"block_until_verdict": 1
})",
connectors_machine_scope());
}
bool use_proto_format() const { return std::get<1>(GetParam()); }
private:
base::test::ScopedFeatureList feature_list;
};
INSTANTIATE_TEST_SUITE_P(,
DownloadRestrictionsDeepScanningBrowserTest,
testing::Combine(testing::Bool(), testing::Bool()));
IN_PROC_BROWSER_TEST_P(DownloadRestrictionsDeepScanningBrowserTest,
ReportsDownloadsBlockedByDownloadRestrictions) {
safe_browsing::FileTypePoliciesTestOverlay scoped_all_file_types_dangerous =
safe_browsing::ScopedMarkAllFilesDangerousForTesting();
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
SetUpReporting();
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
base::FilePath main_file = DownloadPrefs(browser()->profile())
.DownloadPath()
.AppendASCII("zipfile_two_archives.zip");
base::RunLoop run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(run_loop.QuitClosure());
std::set<std::string> zip_types = {"application/zip",
"application/x-zip-compressed"};
if (use_proto_format()) {
chrome::cros::reporting::proto::SafeBrowsingDangerousDownloadEvent
expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("zipfile_two_archives.zip");
#else
connectors_machine_scope()
? expected_event.set_file_name(main_file.AsUTF8Unsafe())
: expected_event.set_file_name("zipfile_two_archives.zip");
#endif
expected_event.set_content_size(276);
expected_event.set_download_digest_sha256("");
expected_event.set_threat_type(
chrome::cros::reporting::proto::SafeBrowsingDangerousDownloadEvent::
DANGEROUS_FILE_TYPE);
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_BLOCKED);
expected_event.set_clicked_through(false);
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectDangerousDownloadEvent(std::move(expected_event),
&zip_types);
} else {
validator.ExpectDangerousDeepScanningResult(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/
connectors_machine_scope() ? main_file.AsUTF8Unsafe()
: "zipfile_two_archives.zip",
// sha256sum chrome/test/data/safe_browsing/download_protection/\
// zipfile_two_archives.zip | tr '[:lower:]' '[:upper:]'
/*sha*/ "",
/*threat_type*/ "DANGEROUS_FILE_TYPE",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*mimetypes*/ &zip_types,
/*size*/ 276,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::BLOCKED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ std::nullopt);
}
WaitForDownloadToFinish();
run_loop.Run();
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
EXPECT_EQ(item->GetState(), download::DownloadItem::INTERRUPTED);
}
class AllowlistedUrlDeepScanningBrowserTest
: public DownloadDeepScanningBrowserTestBase,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
AllowlistedUrlDeepScanningBrowserTest()
: DownloadDeepScanningBrowserTestBase(
/*connectors_machine_scope=*/std::get<0>(GetParam()),
/*is_consumer=*/false,
/*is_obfuscated=*/std::get<1>(GetParam())) {}
~AllowlistedUrlDeepScanningBrowserTest() override = default;
void SetUpOnMainThread() override {
DownloadDeepScanningBrowserTestBase::SetUpOnMainThread();
base::Value::List domain_list;
domain_list.Append(embedded_test_server()->base_url().host());
browser()->profile()->GetPrefs()->SetList(
prefs::kSafeBrowsingAllowlistDomains, std::move(domain_list));
}
};
INSTANTIATE_TEST_SUITE_P(,
AllowlistedUrlDeepScanningBrowserTest,
testing::Combine(
/*connectors_machine_scope=*/testing::Bool(),
/*is_obfuscated=*/testing::Bool()));
IN_PROC_BROWSER_TEST_P(AllowlistedUrlDeepScanningBrowserTest,
AllowlistedUrlStillDoesDlpAndMalware) {
// This allows the blocking DM token reads happening on profile-Connector
// triggers.
base::ScopedAllowBlockingForTesting allow_blocking;
// The file is SAFE according to the metadata check
ClientDownloadResponse metadata_response;
metadata_response.set_verdict(ClientDownloadResponse::SAFE);
ExpectMetadataResponse(metadata_response);
// The DLP scan runs synchronously, and finds a violation.
enterprise_connectors::ContentAnalysisResponse sync_response;
auto* result = sync_response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_rule = result->add_triggered_rules();
dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
// The malware scan also runs synchronously, but finds no violation
result = sync_response.add_results();
result->set_tag("malware");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(sync_response);
GURL url = embedded_test_server()->GetURL(
"/safe_browsing/download_protection/zipfile_two_archives.zip");
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
WaitForDownloadToFinish();
// The file should be blocked for sensitive content.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::
DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK);
EXPECT_EQ(item->GetState(), download::DownloadItem::INTERRUPTED);
}
class WaitForModalObserver : public DeepScanningRequest::Observer {
public:
explicit WaitForModalObserver(DeepScanningRequest* request)
: request_(request),
run_loop_(base::RunLoop::Type::kNestableTasksAllowed) {
request_->AddObserver(this);
}
~WaitForModalObserver() override {
if (request_) {
request_->RemoveObserver(this);
}
}
void Wait() { run_loop_.Run(); }
void OnFinish(DeepScanningRequest* request) override {
request_->RemoveObserver(this);
request_ = nullptr;
}
private:
raw_ptr<DeepScanningRequest> request_;
base::RunLoop run_loop_;
};
class WaitForFinishObserver : public DeepScanningRequest::Observer {
public:
explicit WaitForFinishObserver(DeepScanningRequest* request)
: request_(request),
run_loop_(base::RunLoop::Type::kNestableTasksAllowed) {
request_->AddObserver(this);
}
~WaitForFinishObserver() override {
if (request_) {
request_->RemoveObserver(this);
}
}
void Wait() { run_loop_.Run(); }
void OnFinish(DeepScanningRequest* request) override {
run_loop_.Quit();
request_->RemoveObserver(this);
request_ = nullptr;
}
private:
raw_ptr<DeepScanningRequest> request_;
base::RunLoop run_loop_;
};
class SavePackageDeepScanningBrowserTest
: public DownloadDeepScanningBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
SavePackageDeepScanningBrowserTest()
: DownloadDeepScanningBrowserTestBase(/*connectors_machine_scope=*/true,
/*is_consumer=*/false,
/*is_obfuscated=*/false) {
use_proto_format() ? enabled_features_.push_back(
policy::kUploadRealtimeReportingEventsUsingProto)
: disabled_features_.push_back(
policy::kUploadRealtimeReportingEventsUsingProto);
}
bool use_proto_format() const { return GetParam(); }
base::FilePath GetSaveDir() {
return DownloadPrefs(browser()->profile()).DownloadPath();
}
base::FilePath GetTestFilePath() {
return GetTestDataDirectory().AppendASCII("save_page/text.txt");
}
};
INSTANTIATE_TEST_SUITE_P(, SavePackageDeepScanningBrowserTest, testing::Bool());
IN_PROC_BROWSER_TEST_P(SavePackageDeepScanningBrowserTest, Allowed) {
SetUpReporting();
EXPECT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/save_page/text.txt")));
enterprise_connectors::ContentAnalysisResponse response;
auto* result = response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
ExpectContentAnalysisResumableMetadataResponse({"dlp"});
ExpectContentAnalysisResumableContentResponse(response);
base::RunLoop run_loop;
content::SavePackageFinishedObserver observer(
browser()->profile()->GetDownloadManager(), run_loop.QuitClosure());
base::FilePath main_file = GetSaveDir().AppendASCII("text.htm");
base::FilePath extra_files_dir = GetSaveDir().AppendASCII("text_files");
ASSERT_TRUE(browser()->tab_strip_model()->GetActiveWebContents()->SavePage(
main_file, extra_files_dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML));
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::SAVE_AS_DOWNLOAD);
// The response should not trigger a security event.
enterprise_connectors::test::EventReportValidator validator(client());
validator.ExpectNoReport();
run_loop.Run();
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(
item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE);
EXPECT_EQ(item->GetState(), download::DownloadItem::COMPLETE);
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(main_file));
EXPECT_TRUE(base::ContentsEqual(GetTestFilePath(), main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
}
IN_PROC_BROWSER_TEST_P(SavePackageDeepScanningBrowserTest, Blocked) {
SetUpReporting();
GURL url = embedded_test_server()->GetURL("/save_page/text.txt");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Prepares a scanning response that indicates the file should be blocked.
enterprise_connectors::ContentAnalysisResponse response;
auto* result = response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_verdict = result->add_triggered_rules();
dlp_verdict->set_action(enterprise_connectors::TriggeredRule::BLOCK);
ExpectContentAnalysisResumableMetadataResponse({"dlp"});
ExpectContentAnalysisResumableContentResponse(response);
base::RunLoop run_loop;
content::SavePackageFinishedObserver observer(
browser()->profile()->GetDownloadManager(), run_loop.QuitClosure(),
{download::DownloadItem::INTERRUPTED});
base::FilePath main_file = GetSaveDir().AppendASCII("text.htm");
base::FilePath extra_files_dir = GetSaveDir().AppendASCII("text_files");
ASSERT_TRUE(browser()->tab_strip_model()->GetActiveWebContents()->SavePage(
main_file, extra_files_dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML));
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::SAVE_AS_DOWNLOAD);
// The blocking response should trigger a security event.
base::RunLoop validator_run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(validator_run_loop.QuitClosure());
std::set<std::string> mimetypes = {"text/plain"};
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("text.htm");
#else
expected_event.set_file_name(main_file.AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(54);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_BLOCKED);
expected_event.set_clicked_through(false);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::BLOCK);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ main_file.AsUTF8Unsafe(),
// sha256sum chrome/test/data/save_page/text.txt | tr a-f A-F
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ *result,
/*mimetypes*/ &mimetypes,
/*size*/ 54,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::BLOCKED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
run_loop.Run();
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::
DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK);
EXPECT_EQ(item->GetState(), download::DownloadItem::INTERRUPTED);
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_FALSE(base::PathExists(main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
validator_run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(SavePackageDeepScanningBrowserTest, KeepAfterWarning) {
SetUpReporting();
GURL url = embedded_test_server()->GetURL("/save_page/text.txt");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Prepares a scanning response that indicates the file should warn the user.
enterprise_connectors::ContentAnalysisResponse response;
auto* result = response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_verdict = result->add_triggered_rules();
dlp_verdict->set_action(enterprise_connectors::TriggeredRule::WARN);
ExpectContentAnalysisResumableMetadataResponse({"dlp"});
ExpectContentAnalysisResumableContentResponse(response);
base::RunLoop save_package_run_loop;
content::SavePackageFinishedObserver observer(
browser()->profile()->GetDownloadManager(),
save_package_run_loop.QuitClosure(), {download::DownloadItem::COMPLETE});
base::FilePath main_file = GetSaveDir().AppendASCII("text.htm");
base::FilePath extra_files_dir = GetSaveDir().AppendASCII("text_files");
ASSERT_TRUE(browser()->tab_strip_model()->GetActiveWebContents()->SavePage(
main_file, extra_files_dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML));
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::SAVE_AS_DOWNLOAD);
// The warning response should trigger a security event.
base::RunLoop validator_run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(validator_run_loop.QuitClosure());
std::set<std::string> mimetypes = {"text/plain"};
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("text.htm");
#else
expected_event.set_file_name(main_file.AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(54);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_WARNED);
expected_event.set_clicked_through(false);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::WARN);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ main_file.AsUTF8Unsafe(),
// sha256sum chrome/test/data/save_page/text.txt | tr a-f A-F
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ *result,
/*mimetypes*/ &mimetypes,
/*size*/ 54,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::WARNED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
validator_run_loop.Run();
// The warning has been received but neither "keep" or "discard" has been
// chosen at this point, so the download isn't complete or interrupted and the
// file on disk is still not it its final location.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::
DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING);
EXPECT_EQ(item->GetState(), download::DownloadItem::IN_PROGRESS);
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_FALSE(base::PathExists(main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
// Keeping the save package will generate a second warning event, complete the
// download and move the file to its final destination.
base::RunLoop validator_warn_run_loop;
enterprise_connectors::test::EventReportValidator validator_warn(client());
validator_warn.SetDoneClosure(validator_warn_run_loop.QuitClosure());
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("text.htm");
#else
expected_event.set_file_name(main_file.AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(54);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_BYPASSED);
expected_event.set_clicked_through(true);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::WARN);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator_warn.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator_warn.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ main_file.AsUTF8Unsafe(),
// sha256sum chrome/test/data/save_page/text.txt | tr a-f A-F
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ *result,
/*mimetypes*/ &mimetypes,
/*size*/ 54,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::BYPASSED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
DownloadItemModel model(item);
DownloadCommands(model.GetWeakPtr()).ExecuteCommand(DownloadCommands::KEEP);
save_package_run_loop.Run();
ASSERT_EQ(download_items().size(), 1u);
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
EXPECT_EQ(item->GetState(), download::DownloadItem::COMPLETE);
EXPECT_TRUE(base::PathExists(main_file));
EXPECT_TRUE(base::ContentsEqual(GetTestFilePath(), main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
validator_warn_run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(SavePackageDeepScanningBrowserTest,
DiscardAfterWarning) {
SetUpReporting();
GURL url = embedded_test_server()->GetURL("/save_page/text.txt");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Prepares a scanning response that indicates the file should warn the user.
enterprise_connectors::ContentAnalysisResponse response;
auto* result = response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_verdict = result->add_triggered_rules();
dlp_verdict->set_action(enterprise_connectors::TriggeredRule::WARN);
ExpectContentAnalysisResumableMetadataResponse({"dlp"});
ExpectContentAnalysisResumableContentResponse(response);
base::RunLoop save_package_run_loop;
content::SavePackageFinishedObserver observer(
browser()->profile()->GetDownloadManager(),
save_package_run_loop.QuitClosure(), {download::DownloadItem::CANCELLED});
base::FilePath main_file = GetSaveDir().AppendASCII("text.htm");
base::FilePath extra_files_dir = GetSaveDir().AppendASCII("text_files");
ASSERT_TRUE(browser()->tab_strip_model()->GetActiveWebContents()->SavePage(
main_file, extra_files_dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML));
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::SAVE_AS_DOWNLOAD);
// That warning response should trigger a security event.
base::RunLoop validator_run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(validator_run_loop.QuitClosure());
std::set<std::string> mimetypes = {"text/plain"};
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("text.htm");
#else
expected_event.set_file_name(main_file.AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(54);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_WARNED);
expected_event.set_clicked_through(false);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::WARN);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ main_file.AsUTF8Unsafe(),
// sha256sum chrome/test/data/save_page/text.txt | tr a-f A-F
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ *result,
/*mimetypes*/ &mimetypes,
/*size*/ 54,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::WARNED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
validator_run_loop.Run();
// The warning has been received but neither "keep" or "discard" has been
// chosen at this point, so the download isn't complete or interrupted and the
// file on disk is still not it its final location.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::
DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING);
EXPECT_EQ(item->GetState(), download::DownloadItem::IN_PROGRESS);
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_FALSE(base::PathExists(main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
// Discarding the download will remove it from disk and from download_items().
DownloadItemModel model(item);
DownloadCommands(model.GetWeakPtr())
.ExecuteCommand(DownloadCommands::DISCARD);
save_package_run_loop.Run();
ASSERT_TRUE(download_items().empty());
EXPECT_FALSE(base::PathExists(main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
}
IN_PROC_BROWSER_TEST_P(SavePackageDeepScanningBrowserTest, OpenNow) {
SetUpReporting();
GURL url = embedded_test_server()->GetURL("/save_page/text.txt");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
enterprise_connectors::ContentAnalysisResponse response;
auto* result = response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_verdict = result->add_triggered_rules();
dlp_verdict->set_action(enterprise_connectors::TriggeredRule::BLOCK);
base::RunLoop validator_run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(validator_run_loop.QuitClosure());
std::set<std::string> mimetypes = {"text/plain"};
ExpectContentAnalysisResumableMetadataResponse({"dlp"});
ExpectContentAnalysisResumableContentResponse(response);
base::RunLoop save_package_run_loop;
content::SavePackageFinishedObserver observer(
browser()->profile()->GetDownloadManager(),
save_package_run_loop.QuitClosure(), {download::DownloadItem::COMPLETE});
base::FilePath main_file = GetSaveDir().AppendASCII("text.htm");
base::FilePath extra_files_dir = GetSaveDir().AppendASCII("text_files");
ASSERT_TRUE(browser()->tab_strip_model()->GetActiveWebContents()->SavePage(
main_file, extra_files_dir, content::SAVE_PAGE_TYPE_AS_ONLY_HTML));
WaitForDeepScanRequest();
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("text.htm");
#else
expected_event.set_file_name(main_file.AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(54);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_BLOCKED);
expected_event.set_clicked_through(false);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::BLOCK);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ main_file.AsUTF8Unsafe(),
// sha256sum chrome/test/data/save_page/text.txt | tr a-f A-F
"9789A2E12D50EFA4B891D4EF95C5189FA4C98E34C84E1F8017CD8F574CA035DD",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ *result,
/*mimetypes*/ &mimetypes,
/*size*/ 54,
/*result*/
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::BLOCKED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::SAVE_AS_DOWNLOAD);
// Opening the save package before the async response is obtained will
// generate a warning event once it does come back, complete the
// download and move the file to its final destination.
ASSERT_EQ(download_items().size(), 1u);
download::DownloadItem* item = *download_items().begin();
DownloadItemModel model(item);
model.CompleteSafeBrowsingScan();
save_package_run_loop.Run();
validator_run_loop.Run();
EXPECT_EQ(item->GetDangerType(),
download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
EXPECT_EQ(item->GetState(), download::DownloadItem::COMPLETE);
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(main_file));
EXPECT_TRUE(base::ContentsEqual(GetTestFilePath(), main_file));
EXPECT_FALSE(base::PathExists(extra_files_dir));
}
class FileSystemAccessDeepScanningBrowserTest
: public DownloadDeepScanningBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
FileSystemAccessDeepScanningBrowserTest()
: DownloadDeepScanningBrowserTestBase(/*connectors_machine_scope=*/true,
/*is_consumer=*/false,
/*is_obfuscated=*/false) {
enabled_features_.push_back(
safe_browsing::kEnterpriseFileSystemAccessDeepScan);
use_proto_format() ? enabled_features_.push_back(
policy::kUploadRealtimeReportingEventsUsingProto)
: disabled_features_.push_back(
policy::kUploadRealtimeReportingEventsUsingProto);
}
bool use_proto_format() const { return GetParam(); }
void SetUpOnMainThread() override {
DownloadDeepScanningBrowserTestBase::SetUpOnMainThread();
// Setup a temporary directory for the file save path.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// Have file save path automatically selected by picker.
ui::SelectFileDialog::SetFactory(
std::make_unique<SelectPredeterminedFileDialogFactory>(
std::vector<base::FilePath>{GetTestFilePath()}));
// Navigate to a simple page for FS API calls to run from.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
}
void TearDownOnMainThread() override {
ui::SelectFileDialog::SetFactory(nullptr);
DownloadDeepScanningBrowserTestBase::TearDownOnMainThread();
}
base::FilePath GetTestFilePath() {
return temp_dir_.GetPath().AppendASCII("test_fsa_file.txt");
}
void InitiateWriteViaFSA(content::WebContents* web_contents,
const std::string& content) {
// Script that triggers FSA API write. Executes async and creates promise.
constexpr char js_script[] = R"(
window.fsaPromise = (async (content) => {
const handle = await window.showSaveFilePicker();
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
})($1);
)";
content::ExecuteScriptAsync(web_contents,
content::JsReplace(js_script, content));
}
protected:
base::ScopedTempDir temp_dir_;
};
INSTANTIATE_TEST_SUITE_P(,
FileSystemAccessDeepScanningBrowserTest,
testing::Bool());
// For FSA writes that trigger a block deep scan verdict, write should be
// blocked and the destination file empty.
IN_PROC_BROWSER_TEST_P(FileSystemAccessDeepScanningBrowserTest, BlockedWrite) {
SetUpReporting();
// Prepare the scan response with DLP block and successful malware scan.
enterprise_connectors::ContentAnalysisResponse response;
auto* dlp_result = response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_rule = dlp_result->add_triggered_rules();
dlp_rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
auto* malware_result = response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(response);
// Setup message queue for JS responses.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::DOMMessageQueue message_queue(web_contents);
base::RunLoop validator_run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(validator_run_loop.QuitClosure());
InitiateWriteViaFSA(web_contents, kTestContent);
WaitForDeepScanRequest();
GURL url = embedded_test_server()->GetURL("/title1.html");
std::set<std::string> mimetypes = {"text/plain"};
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"6AE8A75555209FD6C44157C0AED8016E763FF435A19CF186F76863140143FF72");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("test_fsa_file.txt");
#else
expected_event.set_file_name(GetTestFilePath().AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(sizeof(kTestContent) - 1);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_BLOCKED);
expected_event.set_clicked_through(false);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::BLOCK);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ GetTestFilePath().AsUTF8Unsafe(),
// echo -n [kTestContent] | sha256sum | tr a-f A-F
/*sha*/
"6AE8A75555209FD6C44157C0AED8016E763FF435A19CF186F76863140143FF72",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ response.results(0),
/*mimetypes*/ &mimetypes,
/*size*/ sizeof(kTestContent) - 1,
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::BLOCKED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
validator_run_loop.Run();
content::EvalJsResult result =
content::EvalJs(web_contents, "window.fsaPromise");
ASSERT_THAT(result, content::EvalJsResult::IsError());
// TODO(crbug.com/407065784): Improve error message for SB checks.
EXPECT_EQ(result.ExtractError(),
"a JavaScript error: \"AbortError: Blocked by Safe Browsing.\"\n");
// File is created but remains empty due to block.
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(GetTestFilePath()));
EXPECT_EQ(0, base::GetFileSize(GetTestFilePath()));
}
// For FSA writes that trigger a safe deep scan verdict, write should be allowed
// and the destination file populated appropriately.
IN_PROC_BROWSER_TEST_P(FileSystemAccessDeepScanningBrowserTest, AllowedWrite) {
SetUpReporting();
// Prepare the scan response with no DLP or malware rules triggered.
enterprise_connectors::ContentAnalysisResponse response;
auto* dlp_result = response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* malware_result = response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(response);
// Setup message queue for JS responses.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::DOMMessageQueue message_queue(web_contents);
InitiateWriteViaFSA(web_contents, kTestContent);
WaitForDeepScanRequest();
EXPECT_EQ(last_request().reason(),
enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD);
ASSERT_THAT(content::EvalJs(web_contents, "window.fsaPromise"),
content::EvalJsResult::IsOk());
// Checks that file is written successfully.
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(GetTestFilePath()));
std::string file_content;
EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_content));
EXPECT_EQ(file_content, kTestContent);
}
// For FSA writes that trigger a warn deep scan verdict, write should be allowed
// and the destination file populated appropriately.
IN_PROC_BROWSER_TEST_P(FileSystemAccessDeepScanningBrowserTest, WarnedWrite) {
SetUpReporting();
// Prepare the scan response with warn DLP rule triggered.
enterprise_connectors::ContentAnalysisResponse response;
auto* dlp_result = response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
auto* dlp_rule = dlp_result->add_triggered_rules();
dlp_rule->set_action(enterprise_connectors::TriggeredRule::WARN);
auto* malware_result = response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(response);
// Setup message queue for JS responses.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::DOMMessageQueue message_queue(web_contents);
base::RunLoop validator_run_loop;
enterprise_connectors::test::EventReportValidator validator(client());
validator.SetDoneClosure(validator_run_loop.QuitClosure());
InitiateWriteViaFSA(web_contents, kTestContent);
WaitForDeepScanRequest();
GURL url = embedded_test_server()->GetURL("/title1.html");
std::set<std::string> mimetypes = {"text/plain"};
if (use_proto_format()) {
chrome::cros::reporting::proto::DlpSensitiveDataEvent expected_event;
expected_event.set_url(url.spec());
expected_event.set_tab_url(url.spec());
expected_event.set_source("");
expected_event.set_destination("");
expected_event.set_download_digest_sha_256(
"6AE8A75555209FD6C44157C0AED8016E763FF435A19CF186F76863140143FF72");
#if BUILDFLAG(IS_CHROMEOS)
expected_event.set_file_name("test_fsa_file.txt");
#else
expected_event.set_file_name(GetTestFilePath().AsUTF8Unsafe());
#endif
expected_event.set_content_type("text/plain");
expected_event.set_content_size(sizeof(kTestContent) - 1);
expected_event.set_scan_id(last_request().request_token());
expected_event.set_trigger(chrome::cros::reporting::proto::
DataTransferEventTrigger::FILE_DOWNLOAD);
expected_event.set_event_result(
chrome::cros::reporting::proto::EventResult::EVENT_RESULT_WARNED);
expected_event.set_clicked_through(false);
chrome::cros::reporting::proto::TriggeredRuleInfo triggered_rule;
triggered_rule.set_action(
chrome::cros::reporting::proto::TriggeredRuleInfo::WARN);
*expected_event.add_triggered_rule_info() = triggered_rule;
::chrome::cros::reporting::proto::UrlInfo referrers;
referrers.set_url(url.spec());
referrers.set_ip(embedded_test_server()->base_url().host());
*expected_event.add_referrers() = referrers;
expected_event.set_profile_identifier(GetProfileIdentifier());
expected_event.set_profile_user_name(kUserName);
validator.ExpectSensitiveDataEvent(std::move(expected_event));
} else {
validator.ExpectSensitiveDataEvent(
/*url*/ url.spec(),
/*tab_url*/ url.spec(),
/*source*/ "",
/*destination*/ "",
/*filename*/ GetTestFilePath().AsUTF8Unsafe(),
// echo -n [kTestContent] | sha256sum | tr a-f A-F
/*sha*/
"6AE8A75555209FD6C44157C0AED8016E763FF435A19CF186F76863140143FF72",
/*trigger*/
enterprise_connectors::kFileDownloadDataTransferEventTrigger,
/*dlp_verdict*/ response.results(0),
/*mimetypes*/ &mimetypes,
/*size*/ sizeof(kTestContent) - 1,
enterprise_connectors::EventResultToString(
enterprise_connectors::EventResult::WARNED),
/*username*/ kUserName,
/*profile_identifier*/ GetProfileIdentifier(),
/*scan_id*/ last_request().request_token(),
/*content_transfer_method*/ std::nullopt,
/*user_justification*/ std::nullopt);
}
validator_run_loop.Run();
// For warn verdicts, we allow the write to happen as there is currently no
// dialog that allows the user to bypass warnings.
ASSERT_THAT(content::EvalJs(web_contents, "window.fsaPromise"),
content::EvalJsResult::IsOk());
// Checks that file is written successfully.
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(GetTestFilePath()));
std::string file_content;
EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_content));
EXPECT_EQ(file_content, kTestContent);
}
// Fail-open behavior for failed deep scan requests on FSA writes.
IN_PROC_BROWSER_TEST_P(FileSystemAccessDeepScanningBrowserTest,
DeepScanFailure) {
SetUpReporting();
// Configure scan response with failed status.
enterprise_connectors::ContentAnalysisResponse response;
auto* dlp_result = response.add_results();
dlp_result->set_tag("dlp");
dlp_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
auto* malware_result = response.add_results();
malware_result->set_tag("malware");
malware_result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::FAILURE);
ExpectContentAnalysisResumableMetadataResponse({"dlp", "malware"});
ExpectContentAnalysisResumableContentResponse(response);
// Setup message queue for JS responses.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::DOMMessageQueue message_queue(web_contents);
InitiateWriteViaFSA(web_contents, kTestContent);
WaitForDeepScanRequest();
// When scan fails, write should still succeed (fail-open behavior).
ASSERT_THAT(content::EvalJs(web_contents, "window.fsaPromise"),
content::EvalJsResult::IsOk());
base::ScopedAllowBlockingForTesting allow_blocking;
EXPECT_TRUE(base::PathExists(GetTestFilePath()));
std::string file_content;
EXPECT_TRUE(base::ReadFileToString(GetTestFilePath(), &file_content));
EXPECT_EQ(file_content, kTestContent);
}
} // namespace safe_browsing