blob: b21f7e2c1a5c62dbb8f91f38203786750c53d911 [file] [log] [blame]
// Copyright 2024 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/search_aggregator_policy_handler.h"
#include "base/feature_list.h"
#include "base/strings/string_util.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_engine_fields_validators.h"
#include "components/strings/grit/components_strings.h"
#include "url/gurl.h"
namespace policy {
namespace {
bool IsPolicyEnabled() {
// 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::kEnableSearchAggregatorPolicy);
}
bool UrlIsNotHttps(const std::string& policy_name,
const std::string& url,
PolicyErrorMap* errors) {
GURL gurl(url);
if (gurl.SchemeIs(url::kHttpsScheme)) {
return false;
}
errors->AddError(policy_name, IDS_SEARCH_POLICY_SETTINGS_URL_NOT_HTTPS,
url);
return true;
}
// Converts a search aggregator policy value `policy_dict` into a dictionary to
// be saved to prefs, with fields corresponding to `TemplateURLData`.
base::Value SearchAggregatorDictFromPolicyValue(
const base::Value::Dict& policy_dict,
bool featured) {
base::Value::Dict dict;
const std::string* name =
policy_dict.FindString(SearchAggregatorPolicyHandler::kName);
CHECK(name);
dict.Set(DefaultSearchManager::kShortName, *name);
const std::string* shortcut =
policy_dict.FindString(SearchAggregatorPolicyHandler::kShortcut);
CHECK(shortcut);
dict.Set(DefaultSearchManager::kKeyword,
featured ? "@" + *shortcut : *shortcut);
const std::string* search_url =
policy_dict.FindString(SearchAggregatorPolicyHandler::kSearchUrl);
CHECK(search_url);
dict.Set(DefaultSearchManager::kURL, *search_url);
const std::string* suggest_url =
policy_dict.FindString(SearchAggregatorPolicyHandler::kSuggestUrl);
CHECK(suggest_url);
dict.Set(DefaultSearchManager::kSuggestionsURL, *suggest_url);
const std::string* icon_url =
policy_dict.FindString(SearchAggregatorPolicyHandler::kIconUrl);
if (icon_url) {
dict.Set(DefaultSearchManager::kFaviconURL, *icon_url);
}
dict.Set(DefaultSearchManager::kPolicyOrigin,
static_cast<int>(TemplateURLData::PolicyOrigin::kSearchAggregator));
dict.Set(DefaultSearchManager::kEnforcedByPolicy, true);
dict.Set(DefaultSearchManager::kFeaturedByPolicy, featured);
dict.Set(DefaultSearchManager::kIsActive,
static_cast<int>(TemplateURLData::ActiveStatus::kTrue));
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));
}
} // namespace
const char SearchAggregatorPolicyHandler::kIconUrl[] = "icon_url";
const char SearchAggregatorPolicyHandler::kName[] = "name";
const char SearchAggregatorPolicyHandler::kRequireShortcut[] =
"require_shortcut";
const char SearchAggregatorPolicyHandler::kSearchUrl[] = "search_url";
const char SearchAggregatorPolicyHandler::kShortcut[] = "shortcut";
const char SearchAggregatorPolicyHandler::kSuggestUrl[] = "suggest_url";
// TODO(375240486): Define the path to the pref where the policy will be saved.
SearchAggregatorPolicyHandler::SearchAggregatorPolicyHandler(Schema schema)
: SimpleSchemaValidatingPolicyHandler(
key::kEnterpriseSearchAggregatorSettings,
EnterpriseSearchManager::kEnterpriseSearchAggregatorSettingsPrefName,
schema,
policy::SchemaOnErrorStrategy::SCHEMA_ALLOW_UNKNOWN,
SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED) {}
SearchAggregatorPolicyHandler::~SearchAggregatorPolicyHandler() = default;
bool SearchAggregatorPolicyHandler::CheckPolicySettings(
const PolicyMap& policies,
PolicyErrorMap* errors) {
if (!IsPolicyEnabled() || !policies.Get(policy_name())) {
return true;
}
if (!SimpleSchemaValidatingPolicyHandler::CheckPolicySettings(policies,
errors)) {
return false;
}
const base::Value::Dict& search_aggregator =
policies.GetValue(policy_name(), base::Value::Type::DICT)->GetDict();
// Shortcut validation.
const std::string& shortcut = *search_aggregator.FindString(kShortcut);
if (search_engine_fields_validators::ShortcutIsEmpty(policy_name(), shortcut,
errors) ||
search_engine_fields_validators::ShortcutHasWhitespace(
policy_name(), shortcut, errors) ||
search_engine_fields_validators::ShortcutStartsWithAtSymbol(
policy_name(), shortcut, errors) ||
search_engine_fields_validators::
ShortcutEqualsDefaultSearchProviderKeyword(policy_name(), shortcut,
policies, errors)) {
return false;
}
// Name validation.
if (search_engine_fields_validators::NameIsEmpty(
policy_name(), *search_aggregator.FindString(kName), errors)) {
return false;
}
// Search URL validation.
const std::string& search_url = *search_aggregator.FindString(kSearchUrl);
if (search_engine_fields_validators::UrlIsEmpty(policy_name(), search_url,
errors) ||
UrlIsNotHttps(policy_name(), search_url, errors) ||
search_engine_fields_validators::ReplacementStringIsMissingFromUrl(
policy_name(), search_url, errors)) {
return false;
}
// Suggest URL validation.
const std::string& suggest_url = *search_aggregator.FindString(kSuggestUrl);
if (search_engine_fields_validators::UrlIsEmpty(policy_name(), suggest_url,
errors) ||
UrlIsNotHttps(policy_name(), suggest_url, errors)) {
return false;
}
// UrlIsEmpty is not used here because icon_url is optional and so there is no
// need to record an error if it is empty.
const std::string* icon_url = search_aggregator.FindString(kIconUrl);
if (icon_url && !icon_url->empty() &&
UrlIsNotHttps(policy_name(), *icon_url, errors)) {
return false;
}
return true;
}
void SearchAggregatorPolicyHandler::ApplyPolicySettings(
const PolicyMap& policies,
PrefValueMap* prefs) {
if (!IsPolicyEnabled()) {
return;
}
const base::Value* policy_value =
policies.GetValue(policy_name(), base::Value::Type::DICT);
if (!policy_value) {
// Reset search aggregator if policy was reset.
prefs->SetValue(
EnterpriseSearchManager::kEnterpriseSearchAggregatorSettingsPrefName,
base::Value(base::Value::List()));
return;
}
base::Value::List providers;
providers.Append(SearchAggregatorDictFromPolicyValue(policy_value->GetDict(),
/*featured=*/false));
providers.Append(SearchAggregatorDictFromPolicyValue(policy_value->GetDict(),
/*featured=*/true));
prefs->SetValue(
EnterpriseSearchManager::kEnterpriseSearchAggregatorSettingsPrefName,
base::Value(std::move(providers)));
prefs->SetBoolean(
EnterpriseSearchManager::
kEnterpriseSearchAggregatorSettingsRequireShortcutPrefName,
policy_value->GetDict()
.FindBool(SearchAggregatorPolicyHandler::kRequireShortcut)
.value_or(false));
}
} // namespace policy