blob: 0864bee526d05ac327c4b75d569e9d1feb81ecc5 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.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/prefs/scoped_user_pref_update.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::SafeBrowsingPrivateEventRouter;
using ::testing::_;
namespace safe_browsing {
namespace {
base::Value MakeListValue(const std::vector<std::string>& elements) {
base::Value list(base::Value::Type::LIST);
for (const std::string& element : elements)
list.Append(element);
return list;
}
base::Value DefaultConnectorSettings() {
base::Value settings(base::Value::Type::DICTIONARY);
settings.SetKey(enterprise_connectors::kKeyServiceProvider,
base::Value("google"));
settings.SetKey(enterprise_connectors::kKeyEnable,
base::Value(base::Value::Type::LIST));
settings.SetKey(enterprise_connectors::kKeyDisable,
base::Value(base::Value::Type::LIST));
return settings;
}
void InitConnectorPrefIfEmpty(
enterprise_connectors::AnalysisConnector connector) {
ListPrefUpdate settings_list(g_browser_process->local_state(),
ConnectorPref(connector));
DCHECK(settings_list.Get());
if (settings_list->empty())
settings_list->Append(DefaultConnectorSettings());
}
void AddConnectorUrlPattern(enterprise_connectors::AnalysisConnector connector,
bool enable,
base::Value url_list,
base::Value tags) {
InitConnectorPrefIfEmpty(connector);
ListPrefUpdate settings_list(g_browser_process->local_state(),
ConnectorPref(connector));
base::Value& settings = settings_list->GetList()[0];
DCHECK(settings.is_dict());
base::Value* list =
settings.FindListKey(enable ? enterprise_connectors::kKeyEnable
: enterprise_connectors::kKeyDisable);
DCHECK(list);
base::Value list_element(base::Value::Type::DICTIONARY);
list_element.SetKey(enterprise_connectors::kKeyUrlList, std::move(url_list));
list_element.SetKey(enterprise_connectors::kKeyTags, std::move(tags));
list->Append(std::move(list_element));
}
void ClearConnectorUrlPattern(
enterprise_connectors::AnalysisConnector connector,
bool enable,
base::Value tags) {
ListPrefUpdate settings_list(g_browser_process->local_state(),
ConnectorPref(connector));
DCHECK(settings_list.Get());
if (settings_list->empty())
return;
base::Value& settings = settings_list->GetList()[0];
DCHECK(settings.is_dict());
base::Value* list =
settings.FindListKey(enable ? enterprise_connectors::kKeyEnable
: enterprise_connectors::kKeyDisable);
if (!list)
return;
DCHECK(list->is_list());
if (list->GetList().empty())
return;
list->EraseListValueIf([&tags](const base::Value& pattern) {
DCHECK(pattern.is_dict());
const base::Value* pattern_tags =
pattern.FindKey(enterprise_connectors::kKeyTags);
if (!pattern_tags)
return false;
DCHECK(pattern_tags->is_list());
return (*pattern_tags == tags);
});
}
template <typename T>
void SetConnectorField(enterprise_connectors::AnalysisConnector connector,
const char* key,
T value) {
InitConnectorPrefIfEmpty(connector);
ListPrefUpdate settings_list(g_browser_process->local_state(),
ConnectorPref(connector));
base::Value& settings = settings_list->GetList()[0];
DCHECK(settings.is_dict());
settings.SetKey(key, base::Value(std::move(value)));
}
} // namespace
EventReportValidator::EventReportValidator(
policy::MockCloudPolicyClient* client)
: client_(client) {}
EventReportValidator::~EventReportValidator() {
testing::Mock::VerifyAndClearExpectations(client_);
}
void EventReportValidator::ExpectUnscannedFileEvent(
const std::string& expected_url,
const std::string& expected_filename,
const std::string& expected_sha256,
const std::string& expected_trigger,
const std::string& expected_reason,
const std::set<std::string>* expected_mimetypes,
int expected_content_size) {
event_key_ = SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent;
url_ = expected_url;
filename_ = expected_filename;
sha256_ = expected_sha256;
mimetypes_ = expected_mimetypes;
trigger_ = expected_trigger;
unscanned_reason_ = expected_reason;
content_size_ = expected_content_size;
EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
.WillOnce([this](base::Value& report,
base::OnceCallback<void(bool)>& callback) {
ValidateReport(&report);
if (!done_closure_.is_null())
done_closure_.Run();
});
}
void EventReportValidator::ExpectDangerousDeepScanningResult(
const std::string& expected_url,
const std::string& expected_filename,
const std::string& expected_sha256,
const std::string& expected_threat_type,
const std::string& expected_trigger,
const std::set<std::string>* expected_mimetypes,
int expected_content_size) {
event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent;
url_ = expected_url;
filename_ = expected_filename;
sha256_ = expected_sha256;
threat_type_ = expected_threat_type;
mimetypes_ = expected_mimetypes;
trigger_ = expected_trigger;
content_size_ = expected_content_size;
EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
.WillOnce([this](base::Value& report,
base::OnceCallback<void(bool)>& callback) {
ValidateReport(&report);
if (!done_closure_.is_null())
done_closure_.Run();
});
}
void EventReportValidator::ExpectSensitiveDataEvent(
const std::string& expected_url,
const std::string& expected_filename,
const std::string& expected_sha256,
const std::string& expected_trigger,
const ContentAnalysisScanResult& expected_dlp_verdict,
const std::set<std::string>* expected_mimetypes,
int expected_content_size) {
event_key_ = SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent;
url_ = expected_url;
dlp_verdict_ = expected_dlp_verdict;
filename_ = expected_filename;
sha256_ = expected_sha256;
mimetypes_ = expected_mimetypes;
trigger_ = expected_trigger;
clicked_through_ = false;
content_size_ = expected_content_size;
EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
.WillOnce([this](base::Value& report,
base::OnceCallback<void(bool)>& callback) {
ValidateReport(&report);
if (!done_closure_.is_null())
done_closure_.Run();
});
}
void EventReportValidator::
ExpectDangerousDeepScanningResultAndSensitiveDataEvent(
const std::string& expected_url,
const std::string& expected_filename,
const std::string& expected_sha256,
const std::string& expected_threat_type,
const std::string& expected_trigger,
const ContentAnalysisScanResult& expected_dlp_verdict,
const std::set<std::string>* expected_mimetypes,
int expected_content_size) {
event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent;
url_ = expected_url;
filename_ = expected_filename;
sha256_ = expected_sha256;
threat_type_ = expected_threat_type;
trigger_ = expected_trigger;
mimetypes_ = expected_mimetypes;
content_size_ = expected_content_size;
EXPECT_CALL(*client_, UploadRealtimeReport_(_, _))
.WillOnce([this](base::Value& report,
base::OnceCallback<void(bool)>& callback) {
ValidateReport(&report);
})
.WillOnce(
[this, expected_dlp_verdict](
base::Value& report, base::OnceCallback<void(bool)>& callback) {
event_key_ = SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent;
threat_type_ = base::nullopt;
clicked_through_ = false;
dlp_verdict_ = expected_dlp_verdict;
ValidateReport(&report);
if (!done_closure_.is_null())
done_closure_.Run();
});
}
void EventReportValidator::ValidateReport(base::Value* report) {
DCHECK(report);
// Extract the event list.
base::Value* event_list =
report->FindKey(policy::RealtimeReportingJobConfiguration::kEventListKey);
ASSERT_NE(nullptr, event_list);
EXPECT_EQ(base::Value::Type::LIST, event_list->type());
const base::Value::ListView mutable_list = event_list->GetList();
// There should only be 1 event per test.
ASSERT_EQ(1, (int)mutable_list.size());
base::Value wrapper = std::move(mutable_list[0]);
EXPECT_EQ(base::Value::Type::DICTIONARY, wrapper.type());
base::Value* event = wrapper.FindKey(event_key_);
ASSERT_NE(nullptr, event);
ASSERT_EQ(base::Value::Type::DICTIONARY, event->type());
// The event should match the expected values.
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyUrl, url_);
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyFileName, filename_);
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyDownloadDigestSha256,
sha256_);
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyTrigger, trigger_);
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyContentSize,
content_size_);
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyThreatType,
threat_type_);
ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyUnscannedReason,
unscanned_reason_);
ValidateMimeType(event);
ValidateDlpVerdict(event);
}
void EventReportValidator::ValidateMimeType(base::Value* value) {
std::string* type =
value->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyContentType);
if (mimetypes_)
EXPECT_TRUE(base::Contains(*mimetypes_, *type));
else
EXPECT_EQ(nullptr, type);
}
void EventReportValidator::ValidateDlpVerdict(base::Value* value) {
if (!dlp_verdict_.has_value())
return;
ValidateField(value, SafeBrowsingPrivateEventRouter::kKeyClickedThrough,
clicked_through_);
base::Value* triggered_rules =
value->FindListKey(SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleInfo);
ASSERT_NE(nullptr, triggered_rules);
ASSERT_EQ(base::Value::Type::LIST, triggered_rules->type());
base::Value::ListView rules_list = triggered_rules->GetList();
size_t rules_size = rules_list.size();
ASSERT_EQ(rules_size, dlp_verdict_.value().triggers.size());
for (size_t i = 0; i < rules_size; ++i) {
base::Value* rule = &rules_list[i];
ASSERT_EQ(base::Value::Type::DICTIONARY, rule->type());
ValidateDlpRule(rule, dlp_verdict_.value().triggers[i]);
}
}
void EventReportValidator::ValidateDlpRule(
base::Value* value,
const ContentAnalysisTrigger& expected_rule) {
ValidateField(value, SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleAction,
base::Optional<int>(expected_rule.action));
ValidateField(value, SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleName,
expected_rule.name);
int64_t rule_id;
ASSERT_TRUE(base::StringToInt64(expected_rule.id, &rule_id));
ValidateField(value, SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleId,
base::Optional<int>(rule_id));
}
void EventReportValidator::ValidateField(
base::Value* value,
const std::string& field_key,
const base::Optional<std::string>& expected_value) {
if (expected_value.has_value())
ASSERT_EQ(*value->FindStringKey(field_key), expected_value.value());
else
ASSERT_EQ(nullptr, value->FindStringKey(field_key));
}
void EventReportValidator::ValidateField(
base::Value* value,
const std::string& field_key,
const base::Optional<int>& expected_value) {
ASSERT_EQ(value->FindIntKey(field_key), expected_value);
}
void EventReportValidator::ValidateField(
base::Value* value,
const std::string& field_key,
const base::Optional<bool>& expected_value) {
ASSERT_EQ(value->FindBoolKey(field_key), expected_value);
}
void EventReportValidator::SetDoneClosure(base::RepeatingClosure closure) {
done_closure_ = std::move(closure);
}
void SetDlpPolicyForConnectors(CheckContentComplianceValues state) {
// The legacy DLP policy has the following behavior:
// - On uploads, scan everything for DLP if it's enabled unless the URL
// matches kURLsToNotCheckComplianceOfUploadedContent, and scan nothing if
// it is disabled.
// - On downloads, only scan URLs matching
// kURLsToCheckComplianceOfDownloadedContent if it's enabled, otherwise scan
// nothing for DLP.
// This is replicated in the connector policies by adding the wildcard pattern
// on upload connectors with the "dlp" tag in "enable", and by removing any
// "enable" patterns with the "dlp" tag when the policy is disabled.
if (state == CHECK_UPLOADS || state == CHECK_UPLOADS_AND_DOWNLOADS) {
AddConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::FILE_ATTACHED, true,
MakeListValue({"*"}), MakeListValue({"dlp"}));
AddConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY, true,
MakeListValue({"*"}), MakeListValue({"dlp"}));
} else {
ClearUrlsToCheckComplianceOfUploadsForConnectors();
}
if (state != CHECK_DOWNLOADS && state != CHECK_UPLOADS_AND_DOWNLOADS)
ClearUrlsToCheckComplianceOfDownloadsForConnectors();
}
void SetMalwarePolicyForConnectors(SendFilesForMalwareCheckValues state) {
// The legacy Malware policy has the following behavior:
// - On uploads, only scan URLs matching
// kURLsToCheckForMalwareOfUploadedContent if it's enabled, otherwise scan
// nothing for malware.
// - On downloard, scan everything for malware if it's enabled unless the URL
// matches kURLsToNotCheckForMalwareOfDownloadedContent, and scan nothing if
// it's disabled.
// This is replicated in the connector policies by adding the wildcard pattern
// on the download connector with the "malware" tag in "enable", and by
// removing any "enable" patterns with the "malware" tag when the policy is
// disabled.
if (state == SEND_DOWNLOADS || state == SEND_UPLOADS_AND_DOWNLOADS) {
AddConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, true,
MakeListValue({"*"}), MakeListValue({"malware"}));
} else {
ClearUrlsToCheckForMalwareOfDownloadsForConnectors();
}
if (state != SEND_UPLOADS && state != SEND_UPLOADS_AND_DOWNLOADS)
ClearUrlsToCheckForMalwareOfUploadsForConnectors();
}
void SetDelayDeliveryUntilVerdictPolicyForConnectors(
DelayDeliveryUntilVerdictValues state) {
int delay_uploads =
(state == DELAY_UPLOADS || state == DELAY_UPLOADS_AND_DOWNLOADS) ? 1 : 0;
int delay_downloads =
(state == DELAY_DOWNLOADS || state == DELAY_UPLOADS_AND_DOWNLOADS) ? 1
: 0;
SetConnectorField(enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY,
enterprise_connectors::kKeyBlockUntilVerdict,
delay_uploads);
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::kKeyBlockUntilVerdict,
delay_uploads);
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED,
enterprise_connectors::kKeyBlockUntilVerdict,
delay_downloads);
}
void SetAllowPasswordProtectedFilesPolicyForConnectors(
AllowPasswordProtectedFilesValues state) {
bool block_uploads =
state != ALLOW_UPLOADS && state != ALLOW_UPLOADS_AND_DOWNLOADS;
bool block_downloads =
state != ALLOW_DOWNLOADS && state != ALLOW_UPLOADS_AND_DOWNLOADS;
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::kKeyBlockPasswordProtected,
block_uploads);
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED,
enterprise_connectors::kKeyBlockPasswordProtected,
block_downloads);
}
void SetBlockUnsupportedFileTypesPolicyForConnectors(
BlockUnsupportedFiletypesValues state) {
bool block_uploads =
state == BLOCK_UNSUPPORTED_FILETYPES_UPLOADS ||
state == BLOCK_UNSUPPORTED_FILETYPES_UPLOADS_AND_DOWNLOADS;
bool block_downloads =
state == BLOCK_UNSUPPORTED_FILETYPES_DOWNLOADS ||
state == BLOCK_UNSUPPORTED_FILETYPES_UPLOADS_AND_DOWNLOADS;
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::kKeyBlockUnsupportedFileTypes,
block_uploads);
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED,
enterprise_connectors::kKeyBlockUnsupportedFileTypes,
block_downloads);
}
void SetBlockLargeFileTransferPolicyForConnectors(
BlockLargeFileTransferValues state) {
bool block_uploads = state == BLOCK_LARGE_UPLOADS ||
state == BLOCK_LARGE_UPLOADS_AND_DOWNLOADS;
bool block_downloads = state == BLOCK_LARGE_DOWNLOADS ||
state == BLOCK_LARGE_UPLOADS_AND_DOWNLOADS;
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::kKeyBlockLargeFiles, block_uploads);
SetConnectorField(enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED,
enterprise_connectors::kKeyBlockLargeFiles,
block_downloads);
}
void AddUrlsToCheckComplianceOfDownloadsForConnectors(
const std::vector<std::string>& urls) {
AddConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, true,
MakeListValue(urls), MakeListValue({"dlp"}));
}
void AddUrlsToNotCheckComplianceOfUploadsForConnectors(
const std::vector<std::string>& urls) {
for (auto connector :
{enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY}) {
AddConnectorUrlPattern(connector, false, MakeListValue(urls),
MakeListValue({"dlp"}));
}
}
void AddUrlsToCheckForMalwareOfUploadsForConnectors(
const std::vector<std::string>& urls) {
for (auto connector :
{enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY}) {
AddConnectorUrlPattern(connector, true, MakeListValue(urls),
MakeListValue({"malware"}));
}
}
void AddUrlsToNotCheckForMalwareOfDownloadsForConnectors(
const std::vector<std::string>& urls) {
AddConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, false,
MakeListValue(urls), MakeListValue({"malware"}));
}
void AddUrlToListForConnectors(const char* pref_name, const std::string& url) {
if (pref_name == prefs::kURLsToCheckComplianceOfDownloadedContent)
AddUrlsToCheckComplianceOfDownloadsForConnectors({url});
else if (pref_name == prefs::kURLsToNotCheckComplianceOfUploadedContent)
AddUrlsToNotCheckComplianceOfUploadsForConnectors({url});
else if (pref_name == prefs::kURLsToCheckForMalwareOfUploadedContent)
AddUrlsToCheckForMalwareOfUploadsForConnectors({url});
else if (pref_name == prefs::kURLsToNotCheckForMalwareOfDownloadedContent)
AddUrlsToNotCheckForMalwareOfDownloadsForConnectors({url});
else
NOTREACHED();
}
void ClearUrlsToCheckComplianceOfUploadsForConnectors() {
for (auto connector :
{enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY}) {
ClearConnectorUrlPattern(connector, true, MakeListValue({"dlp"}));
}
}
void ClearUrlsToCheckForMalwareOfUploadsForConnectors() {
for (auto connector :
{enterprise_connectors::AnalysisConnector::FILE_ATTACHED,
enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY}) {
ClearConnectorUrlPattern(connector, true, MakeListValue({"malware"}));
}
}
void ClearUrlsToCheckComplianceOfDownloadsForConnectors() {
ClearConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, true,
MakeListValue({"dlp"}));
}
void ClearUrlsToCheckForMalwareOfDownloadsForConnectors() {
ClearConnectorUrlPattern(
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, true,
MakeListValue({"malware"}));
}
} // namespace safe_browsing