blob: 36f0fb8495cec6fb1c9f0a3a4d206aff1dc6e984 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/search_engines/enterprise/site_search_policy_handler.h"
#include <algorithm>
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/policy/core/browser/policy_error_map.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_value_map.h"
#include "components/search_engines/default_search_manager.h"
#include "components/search_engines/enterprise/enterprise_search_manager.h"
#include "components/search_engines/enterprise/search_aggregator_policy_handler.h"
#include "components/search_engines/enterprise/search_engine_fields_validators.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_data.h"
#include "components/strings/grit/components_strings.h"
namespace policy {
namespace {
bool IsAllowUserOverrideFieldEnabled() {
// Check that FeatureList is available as a protection against early startup
// crashes. Some policy providers are initialized very early even before
// base::FeatureList is available, but when policies are finally applied, the
// feature stack is fully initialized. The instance check ensures that the
// final decision is delayed until all features are initialized, without any
// other downstream effect.
return base::FeatureList::GetInstance() &&
base::FeatureList::IsEnabled(
omnibox::kEnableSiteSearchAllowUserOverridePolicy);
}
// Converts a site search policy entry `policy_dict` into a dictionary to be
// saved to prefs, with fields corresponding to `TemplateURLData`.
base::Value SiteSearchDictFromPolicyValue(const base::Value::Dict& policy_dict,
bool featured) {
base::Value::Dict dict;
const std::string* name =
policy_dict.FindString(SiteSearchPolicyHandler::kName);
CHECK(name);
dict.Set(DefaultSearchManager::kShortName, *name);
const std::string* shortcut =
policy_dict.FindString(SiteSearchPolicyHandler::kShortcut);
CHECK(shortcut);
dict.Set(DefaultSearchManager::kKeyword,
featured ? "@" + *shortcut : *shortcut);
const std::string* url =
policy_dict.FindString(SiteSearchPolicyHandler::kUrl);
CHECK(url);
dict.Set(DefaultSearchManager::kURL, *url);
dict.Set(DefaultSearchManager::kFeaturedByPolicy, featured);
dict.Set(DefaultSearchManager::kPolicyOrigin,
static_cast<int>(TemplateURLData::PolicyOrigin::kSiteSearch));
const bool allow_user_override =
policy_dict.FindBool(SiteSearchPolicyHandler::kAllowUserOverride)
.value_or(false);
dict.Set(DefaultSearchManager::kEnforcedByPolicy,
!IsAllowUserOverrideFieldEnabled() || !allow_user_override);
dict.Set(DefaultSearchManager::kIsActive,
static_cast<int>(TemplateURLData::ActiveStatus::kTrue));
// TODO(b/307543761): Create a new field `featured_by_policy` and setting
// according to the corresponding dictionary field.
dict.Set(DefaultSearchManager::kFaviconURL,
TemplateURL::GenerateFaviconURL(GURL(*url)).spec());
dict.Set(DefaultSearchManager::kSafeForAutoReplace, false);
double timestamp = static_cast<double>(
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
dict.Set(DefaultSearchManager::kDateCreated, timestamp);
dict.Set(DefaultSearchManager::kLastModified, timestamp);
return base::Value(std::move(dict));
}
void WarnIfNonHttpsUrl(const std::string& policy_name,
const std::string& url,
PolicyErrorMap* errors) {
GURL gurl(url);
if (!gurl.SchemeIs(url::kHttpsScheme)) {
errors->AddError(policy_name, IDS_SEARCH_POLICY_SETTINGS_URL_NOT_HTTPS,
url);
}
}
bool ShortcutAlreadySeen(
const std::string& policy_name,
const std::string& shortcut,
const base::flat_set<std::string>& shortcuts_already_seen,
PolicyErrorMap* errors,
base::flat_set<std::string>* duplicated_shortcuts) {
if (shortcuts_already_seen.find(shortcut) == shortcuts_already_seen.end()) {
return false;
}
if (duplicated_shortcuts->find(shortcut) == duplicated_shortcuts->end()) {
duplicated_shortcuts->insert(shortcut);
// Only show an error message once per shortcut.
errors->AddError(policy_name,
IDS_POLICY_SITE_SEARCH_SETTINGS_DUPLICATED_SHORTCUT,
shortcut);
}
return true;
}
} // namespace
const char SiteSearchPolicyHandler::kName[] = "name";
const char SiteSearchPolicyHandler::kShortcut[] = "shortcut";
const char SiteSearchPolicyHandler::kUrl[] = "url";
const char SiteSearchPolicyHandler::kFeatured[] = "featured";
const char SiteSearchPolicyHandler::kAllowUserOverride[] =
"allow_user_override";
const int SiteSearchPolicyHandler::kMaxSiteSearchProviders = 100;
const int SiteSearchPolicyHandler::kMaxFeaturedProviders = 3;
SiteSearchPolicyHandler::SiteSearchPolicyHandler(Schema schema)
: SimpleSchemaValidatingPolicyHandler(
key::kSiteSearchSettings,
EnterpriseSearchManager::kSiteSearchSettingsPrefName,
schema,
policy::SchemaOnErrorStrategy::SCHEMA_ALLOW_UNKNOWN,
SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED) {}
SiteSearchPolicyHandler::~SiteSearchPolicyHandler() = default;
bool SiteSearchPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
PolicyErrorMap* errors) {
ignored_shortcuts_.clear();
if (!policies.Get(policy_name())) {
return true;
}
if (!SimpleSchemaValidatingPolicyHandler::CheckPolicySettings(policies,
errors)) {
return false;
}
const base::Value::List& site_search_providers =
policies.GetValue(policy_name(), base::Value::Type::LIST)->GetList();
if (site_search_providers.size() > kMaxSiteSearchProviders) {
errors->AddError(policy_name(),
IDS_POLICY_SITE_SEARCH_SETTINGS_MAX_PROVIDERS_LIMIT_ERROR,
base::NumberToString(kMaxSiteSearchProviders));
return false;
}
int num_featured = std::ranges::count_if(
site_search_providers, [](const base::Value& provider) {
return provider.GetDict().FindBool(kFeatured).value_or(false);
});
if (num_featured > kMaxFeaturedProviders) {
errors->AddError(
policy_name(),
IDS_POLICY_SITE_SEARCH_SETTINGS_MAX_FEATURED_PROVIDERS_LIMIT_ERROR,
base::NumberToString(kMaxFeaturedProviders));
return false;
}
base::flat_set<std::string> shortcuts_already_seen;
base::flat_set<std::string> duplicated_shortcuts;
for (const base::Value& provider : site_search_providers) {
const base::Value::Dict& provider_dict = provider.GetDict();
const std::string& shortcut = *provider_dict.FindString(kShortcut);
const std::string& url = *provider_dict.FindString(kUrl);
bool invalid_entry =
search_engine_fields_validators::ShortcutIsEmpty(policy_name(),
shortcut, errors) ||
search_engine_fields_validators::NameIsEmpty(
policy_name(), *provider_dict.FindString(kName), errors) ||
search_engine_fields_validators::UrlIsEmpty(policy_name(), url,
errors) ||
search_engine_fields_validators::ShortcutHasWhitespace(
policy_name(), shortcut, errors) ||
search_engine_fields_validators::ShortcutStartsWithAtSymbol(
policy_name(), shortcut, errors) ||
search_engine_fields_validators::
ShortcutEqualsSearchAggregatorProviderKeyword(shortcut, policies,
errors) ||
search_engine_fields_validators::
ShortcutEqualsDefaultSearchProviderKeyword(policy_name(), shortcut,
policies, errors) ||
ShortcutAlreadySeen(policy_name(), shortcut, shortcuts_already_seen,
errors, &duplicated_shortcuts) ||
search_engine_fields_validators::ReplacementStringIsMissingFromUrl(
policy_name(), url, errors);
if (invalid_entry) {
ignored_shortcuts_.insert(shortcut);
} else {
WarnIfNonHttpsUrl(policy_name(), url, errors);
}
shortcuts_already_seen.insert(shortcut);
}
// Accept if there is at least one shortcut that should not be ignored.
if (shortcuts_already_seen.size() > ignored_shortcuts_.size()) {
return true;
}
errors->AddError(policy_name(),
IDS_SEARCH_POLICY_SETTINGS_NO_VALID_PROVIDER);
return false;
}
void SiteSearchPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
PrefValueMap* prefs) {
const base::Value* policy_value =
policies.GetValue(policy_name(), base::Value::Type::LIST);
if (!policy_value) {
// Reset site search engines if policy was reset.
prefs->SetValue(EnterpriseSearchManager::kSiteSearchSettingsPrefName,
base::Value(base::Value::List()));
return;
}
CHECK(policy_value->is_list());
base::Value::List providers;
for (const base::Value& item : policy_value->GetList()) {
const base::Value::Dict& policy_dict = item.GetDict();
const std::string& shortcut = *policy_dict.FindString(kShortcut);
if (ignored_shortcuts_.find(shortcut) == ignored_shortcuts_.end()) {
providers.Append(
SiteSearchDictFromPolicyValue(policy_dict, /*featured=*/false));
if (policy_dict.FindBool(kFeatured).value_or(false)) {
providers.Append(SiteSearchDictFromPolicyValue(policy_dict,
/*featured=*/true));
}
}
}
prefs->SetValue(EnterpriseSearchManager::kSiteSearchSettingsPrefName,
base::Value(std::move(providers)));
}
} // namespace policy