| // 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 "chrome/browser/enterprise/connectors/analysis/analysis_service_settings.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/enterprise/connectors/common.h" |
| #include "chrome/browser/enterprise/connectors/service_provider_config.h" |
| #include "components/url_matcher/url_util.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "chrome/browser/enterprise/connectors/analysis/source_destination_matcher_ash.h" |
| #endif |
| |
| namespace enterprise_connectors { |
| |
| AnalysisServiceSettings::AnalysisServiceSettings( |
| const base::Value& settings_value, |
| const ServiceProviderConfig& service_provider_config) { |
| if (!settings_value.is_dict()) |
| return; |
| const base::Value::Dict& settings_dict = settings_value.GetDict(); |
| |
| // The service provider identifier should always be there, and it should match |
| // an existing provider. |
| const std::string* service_provider_name = |
| settings_dict.FindString(kKeyServiceProvider); |
| if (service_provider_name) { |
| service_provider_name_ = *service_provider_name; |
| if (service_provider_config.count(service_provider_name_)) { |
| analysis_config_ = |
| service_provider_config.at(service_provider_name_).analysis; |
| } |
| if (!analysis_config_) { |
| DLOG(ERROR) << "No analysis config for corresponding service provider"; |
| return; |
| } |
| } else { |
| return; |
| } |
| |
| // Add the patterns to the settings, which configures settings.matcher and |
| // settings.*_pattern_settings. No enable patterns implies the settings are |
| // invalid. |
| matcher_ = std::make_unique<url_matcher::URLMatcher>(); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| source_destination_matcher_ = std::make_unique<SourceDestinationMatcherAsh>(); |
| #endif |
| base::MatcherStringPattern::ID id(0); |
| for (auto [key, is_enable] : |
| {std::pair{kKeyEnable, true}, {kKeyDisable, false}}) { |
| const base::Value::List* list = settings_dict.FindList(key); |
| if (list && !list->empty()) { |
| for (const base::Value& value : *list) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict) { |
| continue; |
| } |
| auto* url_list = dict->FindList(kKeyUrlList); |
| auto* source_destination_list = |
| dict->FindList(kKeySourceDestinationList); |
| if (url_list && source_destination_list) { |
| DLOG(ERROR) << kKeyUrlList << " and " << kKeySourceDestinationList |
| << " specified together. Ignoring it."; |
| } else if (url_list) { |
| AddUrlPatternSettings(*dict, is_enable, &id); |
| } else if (source_destination_list) { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| AddSourceDestinationSettings(*dict, is_enable, &id); |
| #else |
| DLOG(ERROR) << kKeySourceDestinationList |
| << " specified on unsupported platform. Ignoring it."; |
| #endif |
| } else { |
| DLOG(ERROR) << "Neither " << kKeyUrlList << " nor " |
| << kKeySourceDestinationList |
| << " found in analysis settings. Ignoring it."; |
| } |
| } |
| } else if (is_enable) { |
| // If nothing is enabled, just return and don't parse anything else. |
| return; |
| } |
| } |
| |
| // The block settings are optional, so a default is used if they can't be |
| // found. |
| block_until_verdict_ = |
| settings_dict.FindInt(kKeyBlockUntilVerdict).value_or(0) |
| ? BlockUntilVerdict::kBlock |
| : BlockUntilVerdict::kNoBlock; |
| block_password_protected_files_ = |
| settings_dict.FindBool(kKeyBlockPasswordProtected).value_or(false); |
| block_large_files_ = |
| settings_dict.FindBool(kKeyBlockLargeFiles).value_or(false); |
| block_unsupported_file_types_ = |
| settings_dict.FindBool(kKeyBlockUnsupportedFileTypes).value_or(false); |
| minimum_data_size_ = settings_dict.FindInt(kKeyMinimumDataSize).value_or(100); |
| |
| const base::Value::List* custom_messages = |
| settings_dict.FindList(kKeyCustomMessages); |
| if (custom_messages) { |
| for (const base::Value& value : *custom_messages) { |
| const base::Value::Dict& dict = value.GetDict(); |
| |
| // As of now, this list will contain one message per tag. At some point, |
| // the server may start sending one message per language/tag pair. If this |
| // is the case, this code should be changed to match the language to |
| // Chrome's UI language. |
| const std::string* tag = dict.FindString(kKeyCustomMessagesTag); |
| if (!tag) |
| continue; |
| |
| CustomMessageData data; |
| |
| const std::string* message = dict.FindString(kKeyCustomMessagesMessage); |
| // This string originates as a protobuf string on the server, which are |
| // utf8 and it's used in the UI where it needs to be encoded as utf16. Do |
| // the conversion now, otherwise code down the line may not be able to |
| // determine if the std::string is ASCII or UTF8 before passing it to the |
| // UI. |
| data.message = base::UTF8ToUTF16(message ? *message : ""); |
| |
| const std::string* url = dict.FindString(kKeyCustomMessagesLearnMoreUrl); |
| data.learn_more_url = url ? GURL(*url) : GURL(); |
| |
| tags_[*tag].custom_message = std::move(data); |
| } |
| } |
| |
| const base::Value::List* require_justification_tags = |
| settings_dict.FindList(kKeyRequireJustificationTags); |
| if (require_justification_tags) { |
| for (const base::Value& tag : *require_justification_tags) { |
| tags_[tag.GetString()].requires_justification = true; |
| } |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| const char* verification_key = kKeyWindowsVerification; |
| #elif BUILDFLAG(IS_MAC) |
| const char* verification_key = kKeyMacVerification; |
| #elif BUILDFLAG(IS_LINUX) |
| const char* verification_key = kKeyLinuxVerification; |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) |
| const base::Value::Dict& dict = settings_value.GetDict(); |
| const base::Value::List* signatures = |
| dict.FindListByDottedPath(verification_key); |
| if (signatures) { |
| for (auto& v : *signatures) { |
| if (v.is_string()) |
| verification_signatures_.push_back(v.GetString()); |
| } |
| } |
| #endif |
| } |
| |
| // static |
| absl::optional<AnalysisServiceSettings::URLPatternSettings> |
| AnalysisServiceSettings::GetPatternSettings( |
| const PatternSettings& patterns, |
| base::MatcherStringPattern::ID match) { |
| // If the pattern exists directly in the map, return its settings. |
| if (patterns.count(match) == 1) |
| return patterns.at(match); |
| |
| // If the pattern doesn't exist in the map, it might mean that it wasn't the |
| // only pattern to correspond to its settings and that the ID added to |
| // the map was the one of the last pattern corresponding to those settings. |
| // This means the next match ID greater than |match| has the correct settings |
| // if it exists. |
| auto next = patterns.upper_bound(match); |
| if (next != patterns.end()) |
| return next->second; |
| |
| return absl::nullopt; |
| } |
| |
| AnalysisSettings AnalysisServiceSettings::GetAnalysisSettingsWithTags( |
| std::map<std::string, TagSettings> tags) const { |
| DCHECK(IsValid()); |
| |
| AnalysisSettings settings; |
| |
| settings.block_until_verdict = block_until_verdict_; |
| settings.block_password_protected_files = block_password_protected_files_; |
| settings.block_large_files = block_large_files_; |
| settings.block_unsupported_file_types = block_unsupported_file_types_; |
| if (is_cloud_analysis()) { |
| CloudAnalysisSettings cloud_settings; |
| cloud_settings.analysis_url = GURL(analysis_config_->url); |
| // We assume all support_tags structs have the same max file size. |
| cloud_settings.max_file_size = |
| analysis_config_->supported_tags[0].max_file_size; |
| DCHECK(cloud_settings.analysis_url.is_valid()); |
| settings.cloud_or_local_settings = |
| CloudOrLocalAnalysisSettings(std::move(cloud_settings)); |
| } else { |
| DCHECK(is_local_analysis()); |
| LocalAnalysisSettings local_settings; |
| local_settings.local_path = analysis_config_->local_path; |
| local_settings.user_specific = analysis_config_->user_specific; |
| local_settings.subject_names = analysis_config_->subject_names; |
| // We assume all support_tags structs have the same max file size. |
| local_settings.max_file_size = |
| analysis_config_->supported_tags[0].max_file_size; |
| local_settings.verification_signatures = verification_signatures_; |
| |
| settings.cloud_or_local_settings = |
| CloudOrLocalAnalysisSettings(std::move(local_settings)); |
| } |
| settings.minimum_data_size = minimum_data_size_; |
| settings.tags = std::move(tags); |
| return settings; |
| } |
| |
| absl::optional<AnalysisSettings> AnalysisServiceSettings::GetAnalysisSettings( |
| const GURL& url) const { |
| if (!IsValid()) |
| return absl::nullopt; |
| |
| DCHECK(matcher_); |
| auto matches = matcher_->MatchURL(url); |
| if (matches.empty()) |
| return absl::nullopt; |
| |
| auto tags = GetTags(matches); |
| if (tags.empty()) |
| return absl::nullopt; |
| |
| return GetAnalysisSettingsWithTags(std::move(tags)); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| absl::optional<AnalysisSettings> AnalysisServiceSettings::GetAnalysisSettings( |
| content::BrowserContext* context, |
| const storage::FileSystemURL& source_url, |
| const storage::FileSystemURL& destination_url) const { |
| if (!IsValid()) |
| return absl::nullopt; |
| DCHECK(source_destination_matcher_); |
| |
| auto matches = |
| source_destination_matcher_->Match(context, source_url, destination_url); |
| if (matches.empty()) |
| return absl::nullopt; |
| |
| auto tags = GetTags(matches); |
| if (tags.empty()) |
| return absl::nullopt; |
| |
| return GetAnalysisSettingsWithTags(std::move(tags)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| bool AnalysisServiceSettings::ShouldBlockUntilVerdict() const { |
| if (!IsValid()) |
| return false; |
| return block_until_verdict_ == BlockUntilVerdict::kBlock; |
| } |
| |
| absl::optional<std::u16string> AnalysisServiceSettings::GetCustomMessage( |
| const std::string& tag) { |
| const auto& element = tags_.find(tag); |
| |
| if (!IsValid() || element == tags_.end() || |
| element->second.custom_message.message.empty()) { |
| return absl::nullopt; |
| } |
| |
| return element->second.custom_message.message; |
| } |
| |
| absl::optional<GURL> AnalysisServiceSettings::GetLearnMoreUrl( |
| const std::string& tag) { |
| const auto& element = tags_.find(tag); |
| |
| if (!IsValid() || element == tags_.end() || |
| element->second.custom_message.learn_more_url.is_empty()) { |
| return absl::nullopt; |
| } |
| |
| return element->second.custom_message.learn_more_url; |
| } |
| |
| bool AnalysisServiceSettings::GetBypassJustificationRequired( |
| const std::string& tag) { |
| return tags_.find(tag) != tags_.end() && tags_.at(tag).requires_justification; |
| } |
| |
| bool AnalysisServiceSettings::is_cloud_analysis() const { |
| return analysis_config_ && analysis_config_->url != nullptr; |
| } |
| |
| bool AnalysisServiceSettings::is_local_analysis() const { |
| return analysis_config_ && analysis_config_->local_path != nullptr; |
| } |
| |
| void AnalysisServiceSettings::AddUrlPatternSettings( |
| const base::Value::Dict& url_settings_dict, |
| bool enabled, |
| base::MatcherStringPattern::ID* id) { |
| DCHECK(id); |
| DCHECK(analysis_config_); |
| if (enabled) |
| DCHECK(disabled_patterns_settings_.empty()); |
| else |
| DCHECK(!enabled_patterns_settings_.empty()); |
| |
| URLPatternSettings setting; |
| |
| const base::Value::List* tags = url_settings_dict.FindList(kKeyTags); |
| if (!tags) |
| return; |
| |
| for (const base::Value& tag : *tags) { |
| if (tag.is_string()) { |
| for (const auto& supported_tag : analysis_config_->supported_tags) { |
| if (tag.GetString() == supported_tag.name) |
| setting.tags.insert(tag.GetString()); |
| } |
| } |
| } |
| |
| // Add the URL patterns to the matcher and store the condition set IDs. |
| const base::Value::List* url_list = url_settings_dict.FindList(kKeyUrlList); |
| if (!url_list) { |
| return; |
| } |
| base::MatcherStringPattern::ID previous_id = *id; |
| url_matcher::util::AddFilters(matcher_.get(), enabled, id, *url_list); |
| |
| if (previous_id == *id) { |
| // No rules were added, so don't save settings, as they would override other |
| // valid settings. |
| return; |
| } |
| |
| if (enabled) |
| enabled_patterns_settings_[*id] = std::move(setting); |
| else |
| disabled_patterns_settings_[*id] = std::move(setting); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void AnalysisServiceSettings::AddSourceDestinationSettings( |
| const base::Value::Dict& source_destination_settings_value, |
| bool enabled, |
| base::MatcherStringPattern::ID* id) { |
| DCHECK(id); |
| DCHECK(analysis_config_); |
| DCHECK(source_destination_matcher_); |
| if (enabled) |
| DCHECK(disabled_patterns_settings_.empty()); |
| else |
| DCHECK(!enabled_patterns_settings_.empty()); |
| |
| URLPatternSettings setting; |
| |
| const base::Value::List* tags = |
| source_destination_settings_value.FindList(kKeyTags); |
| if (!tags) |
| return; |
| |
| for (const base::Value& tag : *tags) { |
| if (tag.is_string()) { |
| for (const auto& supported_tag : analysis_config_->supported_tags) { |
| if (tag.GetString() == supported_tag.name) |
| setting.tags.insert(tag.GetString()); |
| } |
| } |
| } |
| |
| // Add the source destination rules to the source_destination_matcher and |
| // store the condition set IDs. |
| const base::Value::List* source_destination_list = |
| source_destination_settings_value.FindList(kKeySourceDestinationList); |
| if (!source_destination_list) { |
| return; |
| } |
| |
| base::MatcherStringPattern::ID previous_id = *id; |
| source_destination_matcher_->AddFilters(id, source_destination_list); |
| if (previous_id == *id) { |
| // No rules were added, so don't save settings, as they would override other |
| // valid settings. |
| return; |
| } |
| |
| if (enabled) |
| enabled_patterns_settings_[*id] = std::move(setting); |
| else |
| disabled_patterns_settings_[*id] = std::move(setting); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| std::map<std::string, TagSettings> AnalysisServiceSettings::GetTags( |
| const std::set<base::MatcherStringPattern::ID>& matches) const { |
| std::set<std::string> enable_tags; |
| std::set<std::string> disable_tags; |
| for (const base::MatcherStringPattern::ID match : matches) { |
| // Enabled patterns need to be checked first, otherwise they always match |
| // the first disabled pattern. |
| bool enable = true; |
| auto maybe_pattern_setting = |
| GetPatternSettings(enabled_patterns_settings_, match); |
| if (!maybe_pattern_setting.has_value()) { |
| maybe_pattern_setting = |
| GetPatternSettings(disabled_patterns_settings_, match); |
| enable = false; |
| } |
| |
| DCHECK(maybe_pattern_setting.has_value()); |
| auto tags = std::move(maybe_pattern_setting.value().tags); |
| if (enable) |
| enable_tags.insert(tags.begin(), tags.end()); |
| else |
| disable_tags.insert(tags.begin(), tags.end()); |
| } |
| |
| for (const std::string& tag_to_disable : disable_tags) |
| enable_tags.erase(tag_to_disable); |
| |
| std::map<std::string, TagSettings> output; |
| for (const std::string& tag : enable_tags) { |
| if (tags_.count(tag)) |
| output[tag] = tags_.at(tag); |
| else |
| output[tag] = TagSettings(); |
| } |
| |
| return output; |
| } |
| |
| bool AnalysisServiceSettings::IsValid() const { |
| // The settings are invalid if no provider was given. |
| if (!analysis_config_) |
| return false; |
| |
| // The settings are invalid if no enabled pattern(s) exist since that would |
| // imply no URL can ever have an analysis. |
| if (enabled_patterns_settings_.empty()) |
| return false; |
| |
| return true; |
| } |
| |
| AnalysisServiceSettings::AnalysisServiceSettings(AnalysisServiceSettings&&) = |
| default; |
| AnalysisServiceSettings::~AnalysisServiceSettings() = default; |
| |
| AnalysisServiceSettings::URLPatternSettings::URLPatternSettings() = default; |
| AnalysisServiceSettings::URLPatternSettings::URLPatternSettings( |
| const AnalysisServiceSettings::URLPatternSettings&) = default; |
| AnalysisServiceSettings::URLPatternSettings::URLPatternSettings( |
| AnalysisServiceSettings::URLPatternSettings&&) = default; |
| AnalysisServiceSettings::URLPatternSettings& |
| AnalysisServiceSettings::URLPatternSettings::operator=( |
| const AnalysisServiceSettings::URLPatternSettings&) = default; |
| AnalysisServiceSettings::URLPatternSettings& |
| AnalysisServiceSettings::URLPatternSettings::operator=( |
| AnalysisServiceSettings::URLPatternSettings&&) = default; |
| AnalysisServiceSettings::URLPatternSettings::~URLPatternSettings() = default; |
| |
| } // namespace enterprise_connectors |