blob: ab61eb7fb38358d65991014414d90860531a911b [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/frame/fenced_frame_permissions_policies.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy_features.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom.h"
namespace blink {
namespace {
// Extracts an Allowlist from a ParsedPermissionsPolicyDeclaration.
PermissionsPolicy::Allowlist AllowlistFromDeclaration(
const ParsedPermissionsPolicyDeclaration& parsed_declaration,
const PermissionsPolicyFeatureList& feature_list) {
auto result = PermissionsPolicy::Allowlist();
if (parsed_declaration.matches_all_origins)
result.AddAll();
if (parsed_declaration.matches_opaque_src)
result.AddOpaqueSrc();
for (const auto& value : parsed_declaration.allowed_origins)
result.Add(value);
return result;
}
} // namespace
PermissionsPolicy::Allowlist::Allowlist() = default;
PermissionsPolicy::Allowlist::Allowlist(const Allowlist& rhs) = default;
PermissionsPolicy::Allowlist::~Allowlist() = default;
void PermissionsPolicy::Allowlist::Add(
const blink::OriginWithPossibleWildcards& origin) {
allowed_origins_.push_back(origin);
}
void PermissionsPolicy::Allowlist::AddAll() {
matches_all_origins_ = true;
}
void PermissionsPolicy::Allowlist::AddOpaqueSrc() {
matches_opaque_src_ = true;
}
bool PermissionsPolicy::Allowlist::Contains(const url::Origin& origin) const {
for (const auto& allowed_origin : allowed_origins_) {
if (allowed_origin.DoesMatchOrigin(origin))
return true;
}
if (origin.opaque())
return matches_opaque_src_;
return matches_all_origins_;
}
bool PermissionsPolicy::Allowlist::MatchesAll() const {
return matches_all_origins_;
}
void PermissionsPolicy::Allowlist::RemoveMatchesAll() {
matches_all_origins_ = false;
}
bool PermissionsPolicy::Allowlist::MatchesOpaqueSrc() const {
return matches_opaque_src_;
}
// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateFromParentPolicy(
const PermissionsPolicy* parent_policy,
const ParsedPermissionsPolicy& container_policy,
const url::Origin& origin) {
return CreateFromParentPolicy(parent_policy, container_policy, origin,
GetPermissionsPolicyFeatureList());
}
// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CopyStateFrom(
const PermissionsPolicy* source) {
if (!source)
return nullptr;
std::unique_ptr<PermissionsPolicy> new_policy =
base::WrapUnique(new PermissionsPolicy(
source->origin_, GetPermissionsPolicyFeatureList()));
new_policy->inherited_policies_ = source->inherited_policies_;
new_policy->allowlists_ = source->allowlists_;
return new_policy;
}
// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateFromParsedPolicy(
const ParsedPermissionsPolicy& parsed_policy,
const url::Origin& origin) {
return CreateFromParsedPolicy(parsed_policy, origin,
GetPermissionsPolicyFeatureList());
}
// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateFromParsedPolicy(
const ParsedPermissionsPolicy& parsed_policy,
const url::Origin& origin,
const PermissionsPolicyFeatureList& features) {
std::unique_ptr<PermissionsPolicy> new_policy =
base::WrapUnique(new PermissionsPolicy(origin, features));
new_policy->SetHeaderPolicy(parsed_policy);
if (!new_policy->allowlists_.empty()) {
new_policy->allowlists_set_by_manifest_ = true;
}
for (const auto& feature : features) {
new_policy->inherited_policies_[feature.first] =
base::Contains(new_policy->allowlists_, feature.first) &&
new_policy->allowlists_[feature.first].Contains(origin);
}
return new_policy;
}
bool PermissionsPolicy::IsFeatureEnabledByInheritedPolicy(
mojom::PermissionsPolicyFeature feature) const {
DCHECK(base::Contains(inherited_policies_, feature));
return inherited_policies_.at(feature);
}
bool PermissionsPolicy::IsFeatureEnabled(
mojom::PermissionsPolicyFeature feature) const {
return IsFeatureEnabledForOrigin(feature, origin_);
}
bool PermissionsPolicy::IsFeatureEnabledForOrigin(
mojom::PermissionsPolicyFeature feature,
const url::Origin& origin) const {
DCHECK(base::Contains(*feature_list_, feature));
DCHECK(base::Contains(inherited_policies_, feature));
auto inherited_value = inherited_policies_.at(feature);
allowlists_checked_ = true;
auto allowlist = allowlists_.find(feature);
if (allowlist != allowlists_.end()) {
return inherited_value && allowlist->second.Contains(origin);
}
// If no "allowlist" is specified, return default feature value.
const PermissionsPolicyFeatureDefault default_policy =
feature_list_->at(feature);
if (default_policy == PermissionsPolicyFeatureDefault::EnableForSelf &&
!origin_.IsSameOriginWith(origin))
return false;
return inherited_value;
}
bool PermissionsPolicy::GetFeatureValueForOrigin(
mojom::PermissionsPolicyFeature feature,
const url::Origin& origin) const {
DCHECK(base::Contains(*feature_list_, feature));
DCHECK(base::Contains(inherited_policies_, feature));
auto inherited_value = inherited_policies_.at(feature);
allowlists_checked_ = true;
auto allowlist = allowlists_.find(feature);
if (allowlist != allowlists_.end()) {
return inherited_value && allowlist->second.Contains(origin);
}
return inherited_value;
}
const PermissionsPolicy::Allowlist PermissionsPolicy::GetAllowlistForDevTools(
mojom::PermissionsPolicyFeature feature) const {
// Return an empty allowlist when disabled through inheritance.
if (!IsFeatureEnabledByInheritedPolicy(feature))
return PermissionsPolicy::Allowlist();
// Return defined policy if exists; otherwise return default policy.
const auto& maybe_allow_list = GetAllowlistForFeatureIfExists(feature);
if (maybe_allow_list.has_value())
return maybe_allow_list.value();
// Note: |allowlists_| purely comes from HTTP header. If a feature is not
// declared in HTTP header, all origins are implicitly allowed.
PermissionsPolicy::Allowlist default_allowlist;
default_allowlist.AddAll();
return default_allowlist;
}
// TODO(crbug.com/937131): Use |PermissionsPolicy::GetAllowlistForDevTools|
// to replace this method. This method uses legacy |default_allowlist|
// calculation method.
const PermissionsPolicy::Allowlist PermissionsPolicy::GetAllowlistForFeature(
mojom::PermissionsPolicyFeature feature) const {
DCHECK(base::Contains(*feature_list_, feature));
// Return an empty allowlist when disabled through inheritance.
if (!IsFeatureEnabledByInheritedPolicy(feature))
return PermissionsPolicy::Allowlist();
// Return defined policy if exists; otherwise return default policy.
const auto& maybe_allow_list = GetAllowlistForFeatureIfExists(feature);
if (maybe_allow_list.has_value())
return maybe_allow_list.value();
const PermissionsPolicyFeatureDefault default_policy =
feature_list_->at(feature);
PermissionsPolicy::Allowlist default_allowlist;
if (default_policy == PermissionsPolicyFeatureDefault::EnableForAll) {
default_allowlist.AddAll();
} else if (default_policy == PermissionsPolicyFeatureDefault::EnableForSelf) {
default_allowlist.Add(
blink::OriginWithPossibleWildcards(origin_,
/*has_subdomain_wildcard=*/false));
}
return default_allowlist;
}
absl::optional<const PermissionsPolicy::Allowlist>
PermissionsPolicy::GetAllowlistForFeatureIfExists(
mojom::PermissionsPolicyFeature feature) const {
// Return an empty allowlist when disabled through inheritance.
if (!IsFeatureEnabledByInheritedPolicy(feature))
return absl::nullopt;
// Only return allowlist if actually in `allowlists_`.
allowlists_checked_ = true;
auto allowlist = allowlists_.find(feature);
if (allowlist != allowlists_.end())
return allowlist->second;
return absl::nullopt;
}
void PermissionsPolicy::SetHeaderPolicy(
const ParsedPermissionsPolicy& parsed_header) {
if (allowlists_set_by_manifest_)
return;
DCHECK(allowlists_.empty() && !allowlists_checked_);
for (const ParsedPermissionsPolicyDeclaration& parsed_declaration :
parsed_header) {
mojom::PermissionsPolicyFeature feature = parsed_declaration.feature;
DCHECK(feature != mojom::PermissionsPolicyFeature::kNotFound);
allowlists_.emplace(
feature, AllowlistFromDeclaration(parsed_declaration, *feature_list_));
}
}
void PermissionsPolicy::SetHeaderPolicyForIsolatedApp(
const ParsedPermissionsPolicy& parsed_header) {
DCHECK(!allowlists_checked_);
for (const ParsedPermissionsPolicyDeclaration& parsed_declaration :
parsed_header) {
mojom::PermissionsPolicyFeature feature = parsed_declaration.feature;
DCHECK(feature != mojom::PermissionsPolicyFeature::kNotFound);
const auto header_allowlist =
AllowlistFromDeclaration(parsed_declaration, *feature_list_);
auto& isolated_app_allowlist = allowlists_.at(feature);
// If the header does not specify further restrictions we do not need to
// modify the policy.
if (header_allowlist.MatchesAll())
continue;
const auto header_allowed_origins = header_allowlist.AllowedOrigins();
// If the manifest allows all origins access to this feature, use the more
// restrictive header policy.
if (isolated_app_allowlist.MatchesAll()) {
// TODO(crbug.com/1336275): Refactor to use Allowlist::clone() after
// clone() is implemented.
isolated_app_allowlist.SetAllowedOrigins(header_allowed_origins);
isolated_app_allowlist.RemoveMatchesAll();
continue;
}
// Otherwise, we use the intersection of origins in the manifest and the
// header.
auto manifest_allowed_origins = isolated_app_allowlist.AllowedOrigins();
std::vector<blink::OriginWithPossibleWildcards> final_allowed_origins;
for (const auto& origin : manifest_allowed_origins) {
if (base::Contains(header_allowed_origins, origin)) {
final_allowed_origins.push_back(origin);
}
}
isolated_app_allowlist.SetAllowedOrigins(final_allowed_origins);
}
}
void PermissionsPolicy::OverwriteHeaderPolicyForClientHints(
const ParsedPermissionsPolicy& parsed_header) {
DCHECK(!allowlists_checked_);
for (const ParsedPermissionsPolicyDeclaration& parsed_declaration :
parsed_header) {
mojom::PermissionsPolicyFeature feature = parsed_declaration.feature;
DCHECK(GetPolicyFeatureToClientHintMap().contains(feature));
allowlists_[feature] =
AllowlistFromDeclaration(parsed_declaration, *feature_list_);
}
}
PermissionsPolicyFeatureState PermissionsPolicy::GetFeatureState() const {
PermissionsPolicyFeatureState feature_state;
for (const auto& pair : GetPermissionsPolicyFeatureList())
feature_state[pair.first] = GetFeatureValueForOrigin(pair.first, origin_);
return feature_state;
}
PermissionsPolicy::PermissionsPolicy(
url::Origin origin,
const PermissionsPolicyFeatureList& feature_list)
: origin_(std::move(origin)),
allowlists_checked_(false),
feature_list_(feature_list) {}
PermissionsPolicy::~PermissionsPolicy() = default;
// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateForFencedFrame(
const url::Origin& origin,
blink::mojom::FencedFrameMode mode) {
return CreateForFencedFrame(origin, GetPermissionsPolicyFeatureList(), mode);
}
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateForFencedFrame(
const url::Origin& origin,
const PermissionsPolicyFeatureList& features,
blink::mojom::FencedFrameMode mode) {
std::unique_ptr<PermissionsPolicy> new_policy =
base::WrapUnique(new PermissionsPolicy(origin, features));
for (const auto& feature : features) {
new_policy->inherited_policies_[feature.first] = false;
}
// TODO(crbug.com/1347953): this is a medium-term solution to allow
// attribution reporting inside an opaque ad. This will eventually be replaced
// by urn:uuid bound attributes as outlined in this document:
// https://docs.google.com/document/d/11QaI40IAr12CDFrIUQbugxmS9LfircghHUghW-EDzMk/edit?usp=sharing
if (mode == blink::mojom::FencedFrameMode::kOpaqueAds) {
for (const blink::mojom::PermissionsPolicyFeature feature :
blink::kFencedFrameOpaqueAdsDefaultAllowedFeatures) {
new_policy->inherited_policies_[feature] = true;
}
}
return new_policy;
}
// static
std::unique_ptr<PermissionsPolicy> PermissionsPolicy::CreateFromParentPolicy(
const PermissionsPolicy* parent_policy,
const ParsedPermissionsPolicy& container_policy,
const url::Origin& origin,
const PermissionsPolicyFeatureList& features) {
// If there is a non-empty container policy, then there must also be a parent
// policy.
DCHECK(parent_policy || container_policy.empty());
std::unique_ptr<PermissionsPolicy> new_policy =
base::WrapUnique(new PermissionsPolicy(origin, features));
for (const auto& feature : features) {
new_policy->inherited_policies_[feature.first] =
new_policy->InheritedValueForFeature(parent_policy, feature,
container_policy);
}
return new_policy;
}
// Implements Permissions Policy 9.7: Define an inherited policy for feature in
// browsing context and 9.8: Define an inherited policy for feature in container
// at origin.
bool PermissionsPolicy::InheritedValueForFeature(
const PermissionsPolicy* parent_policy,
std::pair<mojom::PermissionsPolicyFeature, PermissionsPolicyFeatureDefault>
feature,
const ParsedPermissionsPolicy& container_policy) const {
// 9.7 2: Otherwise [If context is not a nested browsing context,] return
// "Enabled".
if (!parent_policy)
return true;
// 9.8 2: If policy’s inherited policy for feature is "Disabled", return
// "Disabled".
if (!parent_policy->GetFeatureValueForOrigin(feature.first,
parent_policy->origin_))
return false;
// 9.8 3: If feature is present in policy’s declared policy, and the allowlist
// for feature in policy’s declared policy does not match origin, then return
// "Disabled".
if (!parent_policy->GetFeatureValueForOrigin(feature.first, origin_))
return false;
for (const auto& decl : container_policy) {
if (decl.feature == feature.first) {
// 9.8 5.1: If the allowlist for feature in container policy matches
// origin, return "Enabled".
// 9.8 5.2: Otherwise return "Disabled".
return AllowlistFromDeclaration(decl, *feature_list_).Contains(origin_);
}
}
// 9.8 6: If feature’s default allowlist is *, return "Enabled".
if (feature.second == PermissionsPolicyFeatureDefault::EnableForAll)
return true;
// 9.8 7: If feature’s default allowlist is 'self', and origin is same origin
// with container’s node document’s origin, return "Enabled".
// 9.8 8: Otherwise return "Disabled".
return origin_.IsSameOriginWith(parent_policy->origin_);
}
const PermissionsPolicyFeatureList& PermissionsPolicy::GetFeatureList() const {
return *feature_list_;
}
} // namespace blink