1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/cors_util.h"
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "build/chromeos_buildflags.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern_set.h"
namespace extensions {
namespace {
uint16_t GetEffectivePort(const std::string& port_string) {
int port_int = 0;
bool success = base::StringToInt(port_string, &port_int);
// The URLPattern should verify that |port| is a number or "*", so conversion
// should never fail.
DCHECK(success) << port_string;
return port_int;
}
void AddURLPatternSetToList(
const URLPatternSet& pattern_set,
std::vector<network::mojom::CorsOriginPatternPtr>* list,
network::mojom::CorsOriginAccessMatchPriority priority) {
static const char* const kSchemes[] = {
content::kChromeUIScheme,
#if BUILDFLAG(IS_CHROMEOS)
content::kExternalFileScheme,
#endif
extensions::kExtensionScheme,
url::kFileScheme,
url::kFtpScheme,
url::kHttpScheme,
url::kHttpsScheme,
};
for (const URLPattern& pattern : pattern_set) {
for (const char* const scheme : kSchemes) {
if (!pattern.MatchesScheme(scheme))
continue;
network::mojom::CorsDomainMatchMode domain_match_mode =
pattern.match_subdomains()
? network::mojom::CorsDomainMatchMode::kAllowSubdomains
: network::mojom::CorsDomainMatchMode::kDisallowSubdomains;
network::mojom::CorsPortMatchMode port_match_mode =
(pattern.port() == "*")
? network::mojom::CorsPortMatchMode::kAllowAnyPort
: network::mojom::CorsPortMatchMode::kAllowOnlySpecifiedPort;
uint16_t port =
(port_match_mode ==
network::mojom::CorsPortMatchMode::kAllowOnlySpecifiedPort)
? GetEffectivePort(pattern.port())
: 0u;
list->push_back(network::mojom::CorsOriginPattern::New(
scheme, pattern.host(), port, domain_match_mode, port_match_mode,
priority));
}
}
}
} // namespace
std::vector<network::mojom::CorsOriginPatternPtr>
CreateCorsOriginAccessAllowList(const Extension& extension) {
std::vector<network::mojom::CorsOriginPatternPtr> allow_list;
// Permissions declared by the extension.
URLPatternSet origin_permissions =
extension.permissions_data()->GetEffectiveHostPermissions();
AddURLPatternSetToList(
origin_permissions, &allow_list,
network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority);
// Hosts exempted from the enterprise policy blocklist. This allows
// enterprises to add "carve outs" for hosts extensions may be allowed to run
// on. For instance, an enterprise may block "https://*.restricted.example/*",
// but allow "https://not-sensitive.restricted.example". In order for this to
// work, the enterprise allowlist has higher priority than the enterprise
// blocklist.
// The set intersection is necessary to prevent an enterprise policy from
// granting a host permission the extension didn't ask for.
URLPatternSet policy_allowed_host_patterns =
URLPatternSet::CreateIntersection(
extension.permissions_data()->policy_allowed_hosts(),
origin_permissions, URLPatternSet::IntersectionBehavior::kDetailed);
// TODO(crbug.com/40803363): For now, there is (theoretically) no
// overlap between user-blocked sites and user-allowed sites. This means that,
// unlike enterprise policy above, we don't need to add in user-allowed sites
// here (they should already be granted to the extension, and won't be blocked
// by user-blocked sites). We should either guarantee this is the case (with
// DCHECKs) or change this to allow "carve outs" in user host permissions.
// The latter would likely require adding more knobs to the network layer
// since we'd need a more complex hierarchy.
AddURLPatternSetToList(
policy_allowed_host_patterns, &allow_list,
network::mojom::CorsOriginAccessMatchPriority::kMediumPriority);
return allow_list;
}
std::vector<network::mojom::CorsOriginPatternPtr>
CreateCorsOriginAccessBlockList(const Extension& extension) {
std::vector<network::mojom::CorsOriginPatternPtr> block_list;
// Hosts blocked by enterprise policy.
AddURLPatternSetToList(
extension.permissions_data()->policy_blocked_hosts(), &block_list,
network::mojom::CorsOriginAccessMatchPriority::kLowPriority);
// Add hosts blocked by the user. Unintuitively, these are granted *higher*
// precedence than enterprise blocked sites. This isn't because they are
// conceptually more important, but rather because we need them to take
// priority over enterprise allowed sites. Consider the following scenario:
// - An enterprise blocks https://*.restricted.example.
// - The enterprise allows https://non-sensitive.restricted.example
// - The user blocks https://non-sensitive.restricted.example
// Here, the extension should *not* be allowed to run on
// https://non-sensitive.restricted.example; the enterprise said it *may*, but
// the user then denies it access.
// Note also that enterprise extensions are exempt from user host
// restrictions, so there's no risk of users blocking enterprise extensions
// from running on sites.
// We add user host restrictions with the same priority level as enterprise
// host allowances; when a block rule and an allow rule have the same
// priority, the blocking rule wins. We don't add these with "High" priority
// in order to keep that reserved for browser-defined restrictions.
// TODO(crbug.com/40803363): This is a pretty tenuous setup. We may
// just need to plumb more information to the network service.
AddURLPatternSetToList(
extension.permissions_data()->GetUserBlockedHosts(), &block_list,
network::mojom::CorsOriginAccessMatchPriority::kMediumPriority);
GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
block_list.push_back(network::mojom::CorsOriginPattern::New(
webstore_launch_url.scheme(), webstore_launch_url.host(), /*port=*/0,
network::mojom::CorsDomainMatchMode::kAllowSubdomains,
network::mojom::CorsPortMatchMode::kAllowAnyPort,
network::mojom::CorsOriginAccessMatchPriority::kHighPriority));
GURL new_webstore_launch_url = extension_urls::GetNewWebstoreLaunchURL();
block_list.push_back(network::mojom::CorsOriginPattern::New(
new_webstore_launch_url.scheme(), new_webstore_launch_url.host(),
/*port=*/0, network::mojom::CorsDomainMatchMode::kAllowSubdomains,
network::mojom::CorsPortMatchMode::kAllowAnyPort,
network::mojom::CorsOriginAccessMatchPriority::kHighPriority));
// TODO(devlin): Should we also block the webstore update URL here? See
// https://crbug.com/826946 for a related instance.
return block_list;
}
} // namespace extensions
|