[go: nahoru, domu]

blob: b981246b0d6eb03f1f4d207bb474bee2300ad960 [file] [log] [blame]
// Copyright 2013 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/extensions/policy_handlers.h"
#include <stddef.h>
#include <unordered_set>
#include <utility>
#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_management_constants.h"
#include "chrome/browser/extensions/external_policy_loader.h"
#include "components/crx_file/id_util.h"
#include "components/policy/core/browser/policy_error_map.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_value_map.h"
#include "components/strings/grit/components_strings.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_WIN)
#include "base/enterprise_util.h"
#endif
namespace extensions {
namespace {
// Returns true if extensions_ids contains a list of valid extension ids,
// divided by comma.
bool IsValidIdList(const std::string& extension_ids) {
std::vector<base::StringPiece> ids = base::SplitStringPiece(
extension_ids, ",", base::WhitespaceHandling::TRIM_WHITESPACE,
base::SplitResult::SPLIT_WANT_NONEMPTY);
if (ids.size() == 0)
return false;
for (const auto& id : ids) {
if (!crx_file::id_util::IdIsValid(std::string(id)))
return false;
}
return true;
}
// Returns true if URL is valid and uses one of the supported schemes.
bool IsValidUpdateUrl(const std::string& update_url) {
GURL update_gurl(update_url);
if (!update_gurl.is_valid()) {
return false;
}
return update_gurl.SchemeIsHTTPOrHTTPS() || update_gurl.SchemeIsFile();
}
} // namespace
// ExtensionListPolicyHandler implementation -----------------------------------
ExtensionListPolicyHandler::ExtensionListPolicyHandler(const char* policy_name,
const char* pref_path,
bool allow_wildcards)
: policy::ListPolicyHandler(policy_name, base::Value::Type::STRING),
pref_path_(pref_path),
allow_wildcards_(allow_wildcards) {}
ExtensionListPolicyHandler::~ExtensionListPolicyHandler() {}
bool ExtensionListPolicyHandler::CheckListEntry(const base::Value& value) {
const std::string& str = value.GetString();
if (allow_wildcards_ && str == "*")
return true;
// Make sure str is an extension id.
return crx_file::id_util::IdIsValid(str);
}
void ExtensionListPolicyHandler::ApplyList(base::Value::List filtered_list,
PrefValueMap* prefs) {
prefs->SetValue(pref_path_, base::Value(std::move(filtered_list)));
}
// ExtensionInstallForceListPolicyHandler implementation -----------------------
ExtensionInstallForceListPolicyHandler::ExtensionInstallForceListPolicyHandler()
: policy::TypeCheckingPolicyHandler(policy::key::kExtensionInstallForcelist,
base::Value::Type::LIST) {}
bool ExtensionInstallForceListPolicyHandler::CheckPolicySettings(
const policy::PolicyMap& policies,
policy::PolicyErrorMap* errors) {
const base::Value* value;
return CheckAndGetValue(policies, errors, &value) &&
ParseList(value, nullptr, errors);
}
void ExtensionInstallForceListPolicyHandler::ApplyPolicySettings(
const policy::PolicyMap& policies,
PrefValueMap* prefs) {
const base::Value* value = nullptr;
base::Value::Dict dict;
if (CheckAndGetValue(policies, nullptr, &value) && value &&
ParseList(value, &dict, nullptr)) {
prefs->SetValue(pref_names::kInstallForceList,
base::Value(std::move(dict)));
}
}
bool ExtensionInstallForceListPolicyHandler::ParseList(
const base::Value* policy_value,
base::Value::Dict* extension_dict,
policy::PolicyErrorMap* errors) {
if (!policy_value)
return true;
if (!policy_value->is_list()) {
// This should have been caught in CheckPolicySettings.
NOTREACHED();
return false;
}
int index = -1;
for (const auto& entry : policy_value->GetList()) {
++index;
if (!entry.is_string()) {
if (errors) {
errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
base::Value::GetTypeName(base::Value::Type::STRING),
policy::PolicyErrorPath{index});
}
continue;
}
std::string entry_string = entry.GetString();
// Each string item of the list should be of one of the following forms:
// * <extension_id>
// * <extension_id>;<update_url>
// Note: The update URL might also contain semicolons.
std::string extension_id;
std::string update_url;
size_t pos = entry_string.find(';');
if (pos == std::string::npos) {
extension_id = entry_string;
update_url = extension_urls::kChromeWebstoreUpdateURL;
} else {
extension_id = entry_string.substr(0, pos);
update_url = entry_string.substr(pos + 1);
}
if (!crx_file::id_util::IdIsValid(extension_id)) {
if (errors) {
errors->AddError(policy_name(), IDS_POLICY_INVALID_EXTENSION_ID_ERROR,
policy::PolicyErrorPath{index});
}
continue;
}
// Check that url is valid and uses one of the supported schemes.
if (!IsValidUpdateUrl(update_url)) {
if (errors) {
errors->AddError(policy_name(), IDS_POLICY_INVALID_UPDATE_URL_ERROR,
extension_id, policy::PolicyErrorPath{index});
}
continue;
}
if (extension_dict) {
ExternalPolicyLoader::AddExtension(*extension_dict, extension_id,
update_url);
}
}
return true;
}
base::Value::Dict ExtensionInstallForceListPolicyHandler::GetPolicyDict(
const policy::PolicyMap& policies) {
if (CheckPolicySettings(policies, nullptr)) {
PrefValueMap pref_value_map;
ApplyPolicySettings(policies, &pref_value_map);
const base::Value* value;
if (pref_value_map.GetValue(extensions::pref_names::kInstallForceList,
&value) &&
value->is_dict()) {
return value->GetDict().Clone();
}
}
return base::Value::Dict();
}
// ExtensionURLPatternListPolicyHandler implementation -------------------------
ExtensionURLPatternListPolicyHandler::ExtensionURLPatternListPolicyHandler(
const char* policy_name,
const char* pref_path)
: policy::TypeCheckingPolicyHandler(policy_name, base::Value::Type::LIST),
pref_path_(pref_path) {}
ExtensionURLPatternListPolicyHandler::~ExtensionURLPatternListPolicyHandler() {}
bool ExtensionURLPatternListPolicyHandler::CheckPolicySettings(
const policy::PolicyMap& policies,
policy::PolicyErrorMap* errors) {
const base::Value* value = nullptr;
if (!CheckAndGetValue(policies, errors, &value))
return false;
if (!value)
return true;
if (!value->is_list()) {
NOTREACHED();
return false;
}
// Check that the list contains valid URLPattern strings only.
int index = 0;
for (const auto& entry : value->GetList()) {
if (!entry.is_string()) {
errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
base::Value::GetTypeName(base::Value::Type::STRING),
policy::PolicyErrorPath{index});
return false;
}
std::string url_pattern_string = entry.GetString();
URLPattern pattern(URLPattern::SCHEME_ALL);
if (pattern.Parse(url_pattern_string) !=
URLPattern::ParseResult::kSuccess) {
errors->AddError(policy_name(), IDS_POLICY_INVALID_URL_ERROR,
policy::PolicyErrorPath{index});
return false;
}
++index;
}
return true;
}
void ExtensionURLPatternListPolicyHandler::ApplyPolicySettings(
const policy::PolicyMap& policies,
PrefValueMap* prefs) {
if (!pref_path_)
return;
// It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
const base::Value* value = policies.GetValueUnsafe(policy_name());
if (value)
prefs->SetValue(pref_path_, value->Clone());
}
// ExtensionSettingsPolicyHandler implementation ------------------------------
ExtensionSettingsPolicyHandler::ExtensionSettingsPolicyHandler(
const policy::Schema& chrome_schema)
: policy::SchemaValidatingPolicyHandler(
policy::key::kExtensionSettings,
chrome_schema.GetKnownProperty(policy::key::kExtensionSettings),
policy::SCHEMA_ALLOW_UNKNOWN) {
}
ExtensionSettingsPolicyHandler::~ExtensionSettingsPolicyHandler() {
}
void ExtensionSettingsPolicyHandler::SanitizePolicySettings(
base::Value* policy_value,
policy::PolicyErrorMap* errors) {
DCHECK(policy_value);
// |policy_value| is expected to conform to the defined schema. But it's
// not strictly valid since there are additional restrictions.
DCHECK(policy_value->is_dict());
// Dictionary entries with any invalid setting get removed at the end. We
// can't mutate the dict while iterating, so store them here.
std::unordered_set<std::string> invalid_keys;
// Check each entry, populating |invalid_keys| and |errors|.
for (const auto [extension_ids, policy] : policy_value->GetDict()) {
DCHECK(extension_ids == schema_constants::kWildcard ||
IsValidIdList(extension_ids));
DCHECK(policy.is_dict());
// Extracts sub dictionary.
const base::Value::Dict& sub_dict = policy.GetDict();
const std::string* installation_mode =
sub_dict.FindString(schema_constants::kInstallationMode);
if (installation_mode) {
if (*installation_mode == schema_constants::kForceInstalled ||
*installation_mode == schema_constants::kNormalInstalled) {
DCHECK(extension_ids != schema_constants::kWildcard);
// Verifies that 'update_url' is specified for 'force_installed' and
// 'normal_installed' mode.
const std::string* update_url =
sub_dict.FindString(schema_constants::kUpdateUrl);
if (!update_url || update_url->empty()) {
if (errors) {
errors->AddError(policy_name(), IDS_POLICY_NOT_SPECIFIED_ERROR,
policy::PolicyErrorPath{
extension_ids, schema_constants::kUpdateUrl});
}
invalid_keys.insert(extension_ids);
continue;
}
// Check that url is valid and uses one of the supported schemes.
if (!IsValidUpdateUrl(*update_url)) {
if (errors) {
errors->AddError(policy_name(), IDS_POLICY_INVALID_UPDATE_URL_ERROR,
extension_ids);
}
invalid_keys.insert(extension_ids);
continue;
}
}
}
// Host keys that don't support user defined paths.
const char* host_keys[] = {schema_constants::kPolicyBlockedHosts,
schema_constants::kPolicyAllowedHosts};
const int extension_scheme_mask =
URLPattern::GetValidSchemeMaskForExtensions();
for (const char* key : host_keys) {
const base::Value::List* unparsed_urls = sub_dict.FindList(key);
if (unparsed_urls != nullptr) {
for (const auto& url_value : *unparsed_urls) {
const std::string& unparsed_url = url_value.GetString();
URLPattern pattern(extension_scheme_mask);
URLPattern::ParseResult parse_result = pattern.Parse(unparsed_url);
// These keys don't support paths due to how we track the initiator
// of a webRequest and cookie security policy. We expect a valid
// pattern to return a PARSE_ERROR_EMPTY_PATH.
if (parse_result == URLPattern::ParseResult::kEmptyPath) {
// Add a wildcard path to the URL as it should match any path.
parse_result = pattern.Parse(unparsed_url + "/*");
} else if (parse_result == URLPattern::ParseResult::kSuccess) {
// The user supplied a path, notify them that this is not supported.
if (!pattern.match_all_urls()) {
if (errors) {
errors->AddError(
policy_name(), IDS_POLICY_URL_PATH_SPECIFIED_ERROR,
unparsed_url, policy::PolicyErrorPath{extension_ids, key});
}
invalid_keys.insert(extension_ids);
break;
}
}
if (parse_result != URLPattern::ParseResult::kSuccess) {
if (errors) {
errors->AddError(policy_name(), IDS_POLICY_INVALID_URL_ERROR,
policy::PolicyErrorPath{extension_ids, key});
}
invalid_keys.insert(extension_ids);
break;
}
}
}
}
const base::Value::List* runtime_blocked_hosts =
sub_dict.FindList(schema_constants::kPolicyBlockedHosts);
if (runtime_blocked_hosts != nullptr &&
runtime_blocked_hosts->size() >
schema_constants::kMaxItemsURLPatternSet) {
if (errors) {
policy::PolicyErrorPath error_path = {
extension_ids, schema_constants::kPolicyBlockedHosts};
errors->AddError(
policy_name(), IDS_POLICY_EXTENSION_SETTINGS_ORIGIN_LIMIT_WARNING,
base::NumberToString(schema_constants::kMaxItemsURLPatternSet),
error_path);
}
}
const base::Value::List* runtime_allowed_hosts =
sub_dict.FindList(schema_constants::kPolicyAllowedHosts);
if (runtime_allowed_hosts != nullptr &&
runtime_allowed_hosts->size() >
schema_constants::kMaxItemsURLPatternSet) {
if (errors) {
policy::PolicyErrorPath error_path = {
extension_ids, schema_constants::kPolicyAllowedHosts};
errors->AddError(
policy_name(), IDS_POLICY_EXTENSION_SETTINGS_ORIGIN_LIMIT_WARNING,
base::NumberToString(schema_constants::kMaxItemsURLPatternSet),
error_path);
}
}
}
// Remove |invalid_keys| from the dictionary.
for (const std::string& key : invalid_keys)
policy_value->GetDict().Remove(key);
}
bool ExtensionSettingsPolicyHandler::CheckPolicySettings(
const policy::PolicyMap& policies,
policy::PolicyErrorMap* errors) {
std::unique_ptr<base::Value> policy_value;
if (!CheckAndGetValue(policies, errors, &policy_value))
return false;
if (!policy_value)
return true;
SanitizePolicySettings(policy_value.get(), errors);
return true;
}
void ExtensionSettingsPolicyHandler::ApplyPolicySettings(
const policy::PolicyMap& policies,
PrefValueMap* prefs) {
std::unique_ptr<base::Value> policy_value;
if (!CheckAndGetValue(policies, nullptr, &policy_value) || !policy_value)
return;
SanitizePolicySettings(policy_value.get(), nullptr);
prefs->SetValue(pref_names::kExtensionManagement,
base::Value::FromUniquePtrValue(std::move(policy_value)));
}
} // namespace extensions