| // 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 |