blob: f4d943677a26f61e9693c78d9138a39f198f101d [file] [log] [blame]
// Copyright 2025 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/proxy_config/proxy_override_rules_policy_handler.h"
#include <variant>
#include "base/check.h"
#include "components/policy/core/browser/configuration_policy_handler.h"
#include "components/policy/core/browser/policy_error_map.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_value_map.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "components/proxy_config/proxy_prefs_utils.h"
#include "components/strings/grit/components_strings.h"
#include "net/base/scheme_host_port_matcher_rule.h"
namespace proxy_config {
namespace {
policy::PolicyErrorPath CreateNewPath(
policy::PolicyErrorPath path,
std::variant<int, std::string> new_value) {
path.push_back(std::move(new_value));
return path;
}
} // namespace
ProxyOverrideRulesPolicyHandler::ProxyOverrideRulesPolicyHandler(
policy::Schema schema)
: policy::SchemaValidatingPolicyHandler(
policy::key::kProxyOverrideRules,
schema.GetKnownProperty(policy::key::kProxyOverrideRules),
policy::SchemaOnErrorStrategy::SCHEMA_ALLOW_UNKNOWN) {}
ProxyOverrideRulesPolicyHandler::~ProxyOverrideRulesPolicyHandler() = default;
bool ProxyOverrideRulesPolicyHandler::CheckPolicySettings(
const policy::PolicyMap& policies,
policy::PolicyErrorMap* errors) {
if (!policy::SchemaValidatingPolicyHandler::CheckPolicySettings(policies,
errors)) {
return false;
}
const base::Value* value =
policies.GetValue(policy_name(), base::Value::Type::LIST);
if (!value) {
return true;
}
CHECK(value->is_list());
const auto& rules_list = value->GetList();
for (size_t i = 0; i < rules_list.size(); ++i) {
CHECK(rules_list[i].is_dict());
CheckRule(rules_list[i].GetDict(),
CreateNewPath({}, base::checked_cast<int>(i)), errors);
}
return true;
}
void ProxyOverrideRulesPolicyHandler::ApplyPolicySettings(
const policy::PolicyMap& policies,
PrefValueMap* prefs) {
const policy::PolicyMap::Entry* policy = policies.Get(policy_name());
if (!policy) {
return;
}
std::unique_ptr<base::Value> policy_value;
if (!CheckAndGetValue(policies, /*errors=*/nullptr, &policy_value) ||
!policy_value || !policy_value->is_list()) {
return;
}
policy_value->GetList().EraseIf([this](const base::Value& rule) {
return !CheckRule(rule.GetDict(), /*error_path=*/{}, /*errors=*/nullptr);
});
prefs->SetValue(proxy_config::prefs::kProxyOverrideRules,
policy_value->Clone());
#if !BUILDFLAG(IS_CHROMEOS)
prefs->SetInteger(proxy_config::prefs::kProxyOverrideRulesScope,
policy->scope);
#endif // !BUILDFLAG(IS_CHROMEOS)
}
bool ProxyOverrideRulesPolicyHandler::CheckRule(
const base::Value::Dict& value,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
// Evaluate each sub-field of a given rule to get all error messages
// populated instead of returning early.
bool valid = true;
// Mandatory fields.
if (value.contains(kKeyDestinationMatchers)) {
valid &= CheckDestinations(
*value.Find(kKeyDestinationMatchers),
CreateNewPath(error_path, kKeyDestinationMatchers), errors);
} else {
valid = false;
AddError(IDS_POLICY_NOT_SPECIFIED_ERROR,
CreateNewPath(error_path, kKeyDestinationMatchers), errors);
}
if (value.contains(kKeyProxyList)) {
valid &= CheckProxyList(*value.Find(kKeyProxyList),
CreateNewPath(error_path, kKeyProxyList), errors);
} else {
valid = false;
AddError(IDS_POLICY_NOT_SPECIFIED_ERROR,
CreateNewPath(error_path, kKeyProxyList), errors);
}
// Optional fields.
if (value.contains(kKeyExcludeDestinationMatchers)) {
valid &= CheckDestinations(
*value.Find(kKeyExcludeDestinationMatchers),
CreateNewPath(error_path, kKeyExcludeDestinationMatchers), errors);
}
if (value.contains(kKeyConditions)) {
valid &= CheckConditions(*value.Find(kKeyConditions),
CreateNewPath(error_path, kKeyConditions), errors);
}
return valid;
}
bool ProxyOverrideRulesPolicyHandler::CheckDestinations(
const base::Value& value,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
CHECK(value.is_list());
int i = 0;
for (const auto& destination : value.GetList()) {
CHECK(destination.is_string());
if (!net::SchemeHostPortMatcherRule::FromUntrimmedRawString(
destination.GetString())) {
AddError(IDS_POLICY_PROXY_INVALID_DESTINATION, destination.GetString(),
CreateNewPath(error_path, i), errors);
}
++i;
}
// This function returns true even if invalid are found in the previous loop.
// This is because only a single pattern in the destination list is needed to
// match the "DestinationMatchers" or "ExcludedDestinationMatchers" fields, so
// even if some are misconfigured the rule could still be triggered.
return true;
}
bool ProxyOverrideRulesPolicyHandler::CheckProxyList(
const base::Value& value,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
CHECK(value.is_list());
bool valid = true;
int i = 0;
for (const auto& proxy : value.GetList()) {
CHECK(proxy.is_string());
net::ProxyChain chain =
proxy_config::ProxyOverrideRuleProxyFromString(proxy.GetString());
if (!chain.IsValid()) {
valid = false;
AddError(IDS_POLICY_PROXY_INVALID_PROXY, proxy.GetString(),
CreateNewPath(error_path, i), errors);
}
++i;
}
return valid;
}
bool ProxyOverrideRulesPolicyHandler::CheckConditions(
const base::Value& value,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
CHECK(value.is_list());
bool result = true;
int i = 0;
for (const auto& entry : value.GetList()) {
// Since all conditions in a proxy override rule must be met for a rule to
// match, an invalid condition means the whole rule should never trigger and
// should therefore be ignored. The loop is not exited early to add warnings
// to all conditions.
if (!CheckConditionEntry(entry, CreateNewPath(error_path, i), errors)) {
result = false;
}
++i;
}
return result;
}
bool ProxyOverrideRulesPolicyHandler::CheckConditionEntry(
const base::Value& value,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
CHECK(value.is_dict());
// Conditions have the following format:
// {
// "DnsProbe": {
// "Host": "corp.ads",
// "Result": "resolved", // or "not_found"
// }
// }
// Each field is mandatory.
for (const auto condition : value.GetDict()) {
if (condition.first != kKeyDnsProbe) {
AddError(IDS_POLICY_PROXY_UNKNOWN_CONDITION, condition.first, error_path,
errors);
return false;
}
}
const auto* dns_probe = value.GetDict().FindDict(kKeyDnsProbe);
if (!dns_probe) {
return false;
}
const auto* host = dns_probe->FindString(kKeyHost);
const auto* result = dns_probe->FindString(kKeyResult);
if (!host || !result) {
if (!host) {
AddError(IDS_POLICY_NOT_SPECIFIED_ERROR,
CreateNewPath(error_path, kKeyHost), errors);
}
if (!result) {
AddError(IDS_POLICY_NOT_SPECIFIED_ERROR,
CreateNewPath(error_path, kKeyResult), errors);
}
return false;
}
auto scheme_host_port = proxy_config::ProxyOverrideRuleHostFromString(*host);
if (!scheme_host_port.IsValid()) {
AddError(IDS_POLICY_PROXY_INVALID_SCHEME_HOST_PORT, *host,
CreateNewPath(error_path, kKeyHost), errors);
return false;
}
return true;
}
void ProxyOverrideRulesPolicyHandler::AddError(
int message_id,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
if (!errors) {
return;
}
errors->AddError(policy_name(), message_id, error_path);
}
void ProxyOverrideRulesPolicyHandler::AddError(
int message_id,
const std::string& parameter,
policy::PolicyErrorPath error_path,
policy::PolicyErrorMap* errors) {
if (!errors) {
return;
}
errors->AddError(policy_name(), message_id, parameter, error_path);
}
} // namespace proxy_config