| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // 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/feature_policy/feature_policy.h" |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/no_destructor.h" |
| #include "base/stl_util.h" |
| |
| namespace blink { |
| namespace { |
| |
| // Extracts an Allowlist from a ParsedFeaturePolicyDeclaration. |
| std::unique_ptr<FeaturePolicy::Allowlist> AllowlistFromDeclaration( |
| const ParsedFeaturePolicyDeclaration& parsed_declaration, |
| const FeaturePolicy::FeatureList& feature_list) { |
| mojom::PolicyValueType type = |
| feature_list.at(parsed_declaration.feature).second; |
| std::unique_ptr<FeaturePolicy::Allowlist> result = |
| base::WrapUnique(new FeaturePolicy::Allowlist(type)); |
| result->SetFallbackValue(parsed_declaration.fallback_value); |
| result->SetOpaqueValue(parsed_declaration.opaque_value); |
| for (const auto& value : parsed_declaration.values) |
| result->Add(value.first, value.second); |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| ParsedFeaturePolicyDeclaration::ParsedFeaturePolicyDeclaration() |
| : fallback_value(false), opaque_value(false) {} |
| |
| ParsedFeaturePolicyDeclaration::ParsedFeaturePolicyDeclaration( |
| mojom::FeaturePolicyFeature feature, |
| mojom::PolicyValueType type) |
| : feature(feature), |
| fallback_value(PolicyValue::CreateMinPolicyValue(type)), |
| opaque_value(PolicyValue::CreateMinPolicyValue(type)) {} |
| |
| ParsedFeaturePolicyDeclaration::ParsedFeaturePolicyDeclaration( |
| mojom::FeaturePolicyFeature feature, |
| const std::map<url::Origin, PolicyValue> values, |
| const PolicyValue& fallback_value, |
| const PolicyValue& opaque_value) |
| : feature(feature), |
| values(std::move(values)), |
| fallback_value(std::move(fallback_value)), |
| opaque_value(std::move(opaque_value)) {} |
| |
| ParsedFeaturePolicyDeclaration::ParsedFeaturePolicyDeclaration( |
| const ParsedFeaturePolicyDeclaration& rhs) = default; |
| |
| ParsedFeaturePolicyDeclaration& ParsedFeaturePolicyDeclaration::operator=( |
| const ParsedFeaturePolicyDeclaration& rhs) = default; |
| |
| ParsedFeaturePolicyDeclaration::~ParsedFeaturePolicyDeclaration() = default; |
| |
| bool operator==(const ParsedFeaturePolicyDeclaration& lhs, |
| const ParsedFeaturePolicyDeclaration& rhs) { |
| if (lhs.feature != rhs.feature) |
| return false; |
| if (!(lhs.fallback_value == rhs.fallback_value)) |
| return false; |
| if (!(lhs.opaque_value == rhs.opaque_value)) |
| return false; |
| return lhs.values == rhs.values; |
| } |
| |
| FeaturePolicy::Allowlist::Allowlist(mojom::PolicyValueType type) |
| : fallback_value_(PolicyValue::CreateMinPolicyValue(type)), |
| opaque_value_(PolicyValue::CreateMinPolicyValue(type)) {} |
| |
| FeaturePolicy::Allowlist::Allowlist(const Allowlist& rhs) = default; |
| |
| FeaturePolicy::Allowlist::~Allowlist() = default; |
| |
| void FeaturePolicy::Allowlist::Add(const url::Origin& origin, |
| const PolicyValue& value) { |
| values_[origin] = value; |
| } |
| |
| PolicyValue FeaturePolicy::Allowlist::GetValueForOrigin( |
| const url::Origin& origin) const { |
| // This does not handle the case where origin is an opaque origin, which is |
| // also supposed to exist in the allowlist. (The identical opaque origins |
| // should match in that case) |
| // TODO(iclelland): Fix that, possibly by having another flag for |
| // 'matches_self', which will explicitly match the policy's origin. |
| // https://crbug.com/690520 |
| // |fallback_value_| will either be min (initialized in the parser) value or |
| // set to the corresponding value for * origins. |
| if (origin.opaque()) |
| return opaque_value_; |
| |
| auto value = values_.find(origin); |
| return value == values_.end() ? fallback_value_ : value->second; |
| } |
| |
| const PolicyValue& FeaturePolicy::Allowlist::GetFallbackValue() const { |
| return fallback_value_; |
| } |
| |
| void FeaturePolicy::Allowlist::SetFallbackValue( |
| const PolicyValue& fallback_value) { |
| fallback_value_ = fallback_value; |
| } |
| |
| const PolicyValue& FeaturePolicy::Allowlist::GetOpaqueValue() const { |
| return opaque_value_; |
| } |
| |
| void FeaturePolicy::Allowlist::SetOpaqueValue(const PolicyValue& opaque_value) { |
| opaque_value_ = opaque_value; |
| } |
| |
| const base::flat_map<url::Origin, PolicyValue>& |
| FeaturePolicy::Allowlist::Values() const { |
| return values_; |
| } |
| |
| // static |
| std::unique_ptr<FeaturePolicy> FeaturePolicy::CreateFromParentPolicy( |
| const FeaturePolicy* parent_policy, |
| const ParsedFeaturePolicy& container_policy, |
| const url::Origin& origin) { |
| return CreateFromParentPolicy(parent_policy, container_policy, origin, |
| GetDefaultFeatureList()); |
| } |
| |
| // static |
| std::unique_ptr<FeaturePolicy> FeaturePolicy::CreateWithOpenerPolicy( |
| const FeatureState& inherited_policies, |
| const url::Origin& origin) { |
| std::unique_ptr<FeaturePolicy> new_policy = |
| base::WrapUnique(new FeaturePolicy(origin, GetDefaultFeatureList())); |
| new_policy->inherited_policies_ = inherited_policies; |
| return new_policy; |
| } |
| |
| bool FeaturePolicy::IsFeatureEnabled( |
| mojom::FeaturePolicyFeature feature) const { |
| mojom::PolicyValueType feature_type = feature_list_.at(feature).second; |
| return IsFeatureEnabledForOrigin( |
| feature, origin_, PolicyValue::CreateMaxPolicyValue(feature_type)); |
| } |
| |
| bool FeaturePolicy::IsFeatureEnabled(mojom::FeaturePolicyFeature feature, |
| const PolicyValue& threshold_value) const { |
| return IsFeatureEnabledForOrigin(feature, origin_, threshold_value); |
| } |
| |
| bool FeaturePolicy::IsFeatureEnabledForOrigin( |
| mojom::FeaturePolicyFeature feature, |
| const url::Origin& origin) const { |
| mojom::PolicyValueType feature_type = feature_list_.at(feature).second; |
| return GetFeatureValueForOrigin(feature, origin) >= |
| PolicyValue::CreateMaxPolicyValue(feature_type); |
| } |
| |
| bool FeaturePolicy::IsFeatureEnabledForOrigin( |
| mojom::FeaturePolicyFeature feature, |
| const url::Origin& origin, |
| const PolicyValue& threshold_value) const { |
| return GetFeatureValueForOrigin(feature, origin) >= threshold_value; |
| } |
| |
| PolicyValue FeaturePolicy::GetFeatureValueForOrigin( |
| mojom::FeaturePolicyFeature feature, |
| const url::Origin& origin) const { |
| DCHECK(base::ContainsKey(feature_list_, feature)); |
| DCHECK(base::ContainsKey(inherited_policies_, feature)); |
| |
| auto inherited_value = inherited_policies_.at(feature); |
| auto allowlist = allowlists_.find(feature); |
| if (allowlist != allowlists_.end()) { |
| auto specified_value = allowlist->second->GetValueForOrigin(origin); |
| return PolicyValue::Combine(inherited_value, specified_value); |
| } |
| |
| // If no "allowlist" is specified, return default feature value. |
| // Note that combining value "v" with min value "min_v" is "min_v" and |
| // comining "v" with max value "max_v" is "v". |
| const FeaturePolicy::FeatureDefaultValue default_policy = |
| feature_list_.at(feature); |
| if (default_policy.first == FeaturePolicy::FeatureDefault::DisableForAll || |
| (default_policy.first == FeaturePolicy::FeatureDefault::EnableForSelf && |
| !origin_.IsSameOriginWith(origin))) |
| return PolicyValue::CreateMinPolicyValue(default_policy.second); |
| return inherited_value; |
| } |
| |
| const FeaturePolicy::Allowlist FeaturePolicy::GetAllowlistForFeature( |
| mojom::FeaturePolicyFeature feature) const { |
| DCHECK(base::ContainsKey(feature_list_, feature)); |
| DCHECK(base::ContainsKey(inherited_policies_, feature)); |
| mojom::PolicyValueType type = feature_list_.at(feature).second; |
| // Return an empty allowlist when disabled through inheritance. |
| if (inherited_policies_.at(feature) <= |
| PolicyValue::CreateMinPolicyValue(type)) |
| return FeaturePolicy::Allowlist(type); |
| |
| // Return defined policy if exists; otherwise return default policy. |
| auto allowlist = allowlists_.find(feature); |
| if (allowlist != allowlists_.end()) |
| return FeaturePolicy::Allowlist(*(allowlist->second)); |
| |
| const FeaturePolicy::FeatureDefaultValue default_policy = |
| feature_list_.at(feature); |
| FeaturePolicy::Allowlist default_allowlist(type); |
| |
| if (default_policy.first == FeaturePolicy::FeatureDefault::EnableForAll) { |
| default_allowlist.SetFallbackValue( |
| PolicyValue::CreateMaxPolicyValue(default_policy.second)); |
| } else if (default_policy.first == |
| FeaturePolicy::FeatureDefault::EnableForSelf) { |
| default_allowlist.Add( |
| origin_, PolicyValue::CreateMaxPolicyValue(default_policy.second)); |
| } |
| |
| return default_allowlist; |
| } |
| |
| void FeaturePolicy::SetHeaderPolicy(const ParsedFeaturePolicy& parsed_header) { |
| DCHECK(allowlists_.empty()); |
| for (const ParsedFeaturePolicyDeclaration& parsed_declaration : |
| parsed_header) { |
| mojom::FeaturePolicyFeature feature = parsed_declaration.feature; |
| DCHECK(feature != mojom::FeaturePolicyFeature::kNotFound); |
| allowlists_[feature] = |
| AllowlistFromDeclaration(parsed_declaration, feature_list_); |
| } |
| } |
| |
| FeaturePolicy::FeatureState FeaturePolicy::GetFeatureState() const { |
| FeatureState feature_state; |
| for (const auto& pair : GetDefaultFeatureList()) |
| feature_state[pair.first] = GetFeatureValueForOrigin(pair.first, origin_); |
| return feature_state; |
| } |
| |
| FeaturePolicy::FeaturePolicy(url::Origin origin, |
| const FeatureList& feature_list) |
| : origin_(std::move(origin)), feature_list_(feature_list) { |
| if (origin_.opaque()) { |
| // FeaturePolicy was written expecting opaque Origins to be indistinct, but |
| // this has changed. Split out a new opaque origin here, to defend against |
| // origin-equality. |
| origin_ = origin_.DeriveNewOpaqueOrigin(); |
| } |
| } |
| |
| FeaturePolicy::~FeaturePolicy() = default; |
| |
| // static |
| std::unique_ptr<FeaturePolicy> FeaturePolicy::CreateFromParentPolicy( |
| const FeaturePolicy* parent_policy, |
| const ParsedFeaturePolicy& container_policy, |
| const url::Origin& origin, |
| const FeaturePolicy::FeatureList& 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<FeaturePolicy> new_policy = |
| base::WrapUnique(new FeaturePolicy(origin, features)); |
| // For features which are not keys in a container policy, which is the case |
| // here *until* we call AddContainerPolicy at the end of this method, |
| // https://wicg.github.io/feature-policy/#define-inherited-policy-in-container |
| // returns true if |feature| is enabled in |parent_policy| for |origin|. |
| for (const auto& feature : features) { |
| if (!parent_policy) { |
| // If no parent policy, set inherited policy to max value. |
| new_policy->inherited_policies_[feature.first] = |
| PolicyValue::CreateMaxPolicyValue(feature.second.second); |
| } else { |
| new_policy->inherited_policies_[feature.first] = |
| parent_policy->GetFeatureValueForOrigin(feature.first, origin); |
| } |
| } |
| if (!container_policy.empty()) |
| new_policy->AddContainerPolicy(container_policy, parent_policy); |
| return new_policy; |
| } |
| |
| void FeaturePolicy::AddContainerPolicy( |
| const ParsedFeaturePolicy& container_policy, |
| const FeaturePolicy* parent_policy) { |
| DCHECK(parent_policy); |
| // For features which are keys in a container policy, |
| // https://wicg.github.io/feature-policy/#define-inherited-policy-in-container |
| // returns true only if |feature| is enabled in |parent| for either |origin| |
| // or |parent|'s origin, and the allowlist for |feature| matches |origin|. |
| // |
| // Roughly, If a feature is enabled in the parent frame, and the parent |
| // chooses to delegate it to the child frame, using the iframe attribute, then |
| // the feature should be enabled in the child frame. |
| for (const ParsedFeaturePolicyDeclaration& parsed_declaration : |
| container_policy) { |
| mojom::FeaturePolicyFeature feature = parsed_declaration.feature; |
| // Do not allow setting a container policy for a feature which is not in the |
| // feature list. |
| auto inherited_policy = inherited_policies_.find(feature); |
| if (inherited_policy == inherited_policies_.end()) |
| continue; |
| PolicyValue& inherited_value = inherited_policy->second; |
| // If enabled by |parent_policy| for either |origin| or |parent_policy|'s |
| // origin, then enable in the child iff the declared container policy |
| // matches |origin|. |
| auto parent_value = parent_policy->GetFeatureValueForOrigin( |
| feature, parent_policy->origin_); |
| inherited_value = |
| inherited_value > parent_value ? inherited_value : parent_value; |
| |
| inherited_value.Combine( |
| AllowlistFromDeclaration(parsed_declaration, feature_list_) |
| ->GetValueForOrigin(origin_)); |
| } |
| } |
| |
| const FeaturePolicy::FeatureList& FeaturePolicy::GetFeatureList() const { |
| return feature_list_; |
| } |
| |
| // static |
| // See third_party/blink/public/common/feature_policy/feature_policy.h for |
| // status of each feature (in spec, implemented, etc). |
| // The second field of FeatureDefaultValue is the type of PolicyValue that is |
| // asspcoated with the feature. |
| // TODO(loonybear): replace boolean type value to the actual value type. |
| const FeaturePolicy::FeatureList& FeaturePolicy::GetDefaultFeatureList() { |
| static base::NoDestructor<FeatureList> default_feature_list( |
| {{mojom::FeaturePolicyFeature::kAccelerometer, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kAccessibilityEvents, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kAmbientLightSensor, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kAutoplay, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kCamera, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kDocumentDomain, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kDocumentWrite, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kEncryptedMedia, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kFontDisplay, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kFormSubmission, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kFullscreen, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kGeolocation, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kGyroscope, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kHid, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kUnoptimizedImages, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kIdleDetection, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kLayoutAnimations, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kLazyLoad, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kLegacyImageFormats, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kMagnetometer, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kOversizedImages, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kDecDouble)}, |
| {mojom::FeaturePolicyFeature::kMicrophone, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kMidiFeature, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kModals, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kOrientationLock, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kPayment, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kPictureInPicture, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kPointerLock, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kPopups, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kPresentation, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kScript, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kSerial, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kSpeaker, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kSyncScript, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kSyncXHR, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kTopNavigation, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kUnsizedMedia, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kUsb, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kVerticalScroll, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kWakeLock, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| {mojom::FeaturePolicyFeature::kWebVr, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}, |
| // kFrobulate is a test only feature. |
| {mojom::FeaturePolicyFeature::kFrobulate, |
| FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf, |
| mojom::PolicyValueType::kBool)}}); |
| return *default_feature_list; |
| } |
| |
| } // namespace blink |