blob: ce549e8bd9e3e2bb5b81fabd9017fe5d57cba9ba [file] [log] [blame]
// Copyright 2019 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 "services/network/origin_policy/origin_policy_manager.h"
#include <memory>
#include <utility>
#include "base/logging.h"
#include "base/optional.h"
#include "net/http/http_util.h"
#include "services/network/network_context.h"
#include "services/network/origin_policy/origin_policy_fetcher.h"
namespace {
// Marker for (temporarily) exempted origins. The presence of the "?" guarantees
// that this is not a valid policy as it is not a valid http token.
const char kExemptedOriginPolicyVersion[] = "exception?";
} // namespace
namespace network {
OriginPolicyManager::OriginPolicyManager(NetworkContext* owner_network_context)
: owner_network_context_(owner_network_context),
url_loader_factory_(
owner_network_context_->CreateUrlLoaderFactoryForNetworkService()) {}
OriginPolicyManager::~OriginPolicyManager() {}
void OriginPolicyManager::AddReceiver(
mojo::PendingReceiver<mojom::OriginPolicyManager> receiver) {
receivers_.Add(this, std::move(receiver));
}
void OriginPolicyManager::RetrieveOriginPolicy(
const url::Origin& origin,
const std::string& header_value,
RetrieveOriginPolicyCallback callback) {
DCHECK(origin.GetURL().is_valid());
DCHECK(!origin.opaque());
OriginPolicyHeaderValues header_info =
GetRequestedPolicyAndReportGroupFromHeaderString(header_value);
auto iter = latest_version_map_.find(origin);
// Process policy deletion first!
if (header_info.policy_version == kOriginPolicyDeletePolicy) {
if (iter != latest_version_map_.end())
latest_version_map_.erase(iter);
InvokeCallbackWithPolicyState(origin, OriginPolicyState::kNoPolicyApplies,
std::move(callback));
return;
}
// Process policy exceptions.
if (iter != latest_version_map_.end() &&
iter->second == kExemptedOriginPolicyVersion) {
InvokeCallbackWithPolicyState(origin, OriginPolicyState::kNoPolicyApplies,
std::move(callback));
return;
}
// No policy applies to this request or invalid header present.
if (header_info.policy_version.empty()) {
// If there header has no policy version is present, use cached version, if
// there is one. Otherwise, fail.
if (iter == latest_version_map_.end()) {
InvokeCallbackWithPolicyState(origin,
header_value.empty()
? OriginPolicyState::kNoPolicyApplies
: OriginPolicyState::kCannotLoadPolicy,
std::move(callback));
MaybeReport(OriginPolicyState::kCannotLoadPolicy, header_info, GURL());
return;
}
header_info.policy_version = iter->second;
} else if (iter == latest_version_map_.end()) {
latest_version_map_.emplace(origin, header_info.policy_version);
} else {
iter->second = header_info.policy_version;
}
origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
this, header_info, origin, url_loader_factory_.get(),
std::move(callback)));
}
void OriginPolicyManager::AddExceptionFor(const url::Origin& origin) {
latest_version_map_[origin] = kExemptedOriginPolicyVersion;
}
void OriginPolicyManager::FetcherDone(OriginPolicyFetcher* fetcher,
const OriginPolicy& origin_policy,
RetrieveOriginPolicyCallback callback) {
std::move(callback).Run(origin_policy);
auto it = origin_policy_fetchers_.find(fetcher);
DCHECK(it != origin_policy_fetchers_.end());
origin_policy_fetchers_.erase(it);
}
void OriginPolicyManager::RetrieveDefaultOriginPolicy(
const url::Origin& origin,
RetrieveOriginPolicyCallback callback) {
origin_policy_fetchers_.emplace(std::make_unique<OriginPolicyFetcher>(
this, origin, url_loader_factory_.get(), std::move(callback)));
}
#if BUILDFLAG(ENABLE_REPORTING)
void OriginPolicyManager::MaybeReport(
OriginPolicyState state,
const OriginPolicyHeaderValues& header_info,
const GURL& policy_url) {
if (header_info.report_to.empty())
return;
const char* reason_str = nullptr;
switch (state) {
case OriginPolicyState::kCannotLoadPolicy:
reason_str = "CANNOT_LOAD";
break;
case OriginPolicyState::kInvalidRedirect:
reason_str = "REDIRECT";
break;
case OriginPolicyState::kOther:
reason_str = "OTHER";
break;
default:
NOTREACHED();
return;
}
base::DictionaryValue report_body;
report_body.SetKey("origin_policy_url", base::Value(policy_url.spec()));
report_body.SetKey("policy", base::Value(header_info.raw_header));
report_body.SetKey("policy_error_reason", base::Value(reason_str));
owner_network_context_->QueueReport("origin-policy", header_info.report_to,
policy_url, base::nullopt,
std::move(report_body));
}
#else
void OriginPolicyManager::MaybeReport(
OriginPolicyState state,
const OriginPolicyHeaderValues& header_info,
const GURL& policy_url) {}
#endif // BUILDFLAG(ENABLE_REPORTING)
// static
const char* OriginPolicyManager::GetExemptedVersionForTesting() {
return kExemptedOriginPolicyVersion;
}
// static
OriginPolicyHeaderValues
OriginPolicyManager::GetRequestedPolicyAndReportGroupFromHeaderString(
const std::string& header_value) {
if (net::HttpUtil::TrimLWS(header_value) == kOriginPolicyDeletePolicy)
return OriginPolicyHeaderValues(
{kOriginPolicyDeletePolicy, "", header_value});
base::Optional<std::string> policy;
base::Optional<std::string> report_to;
bool valid = true;
net::HttpUtil::NameValuePairsIterator iter(header_value.cbegin(),
header_value.cend(), ',');
while (iter.GetNext()) {
std::string token_value = net::HttpUtil::TrimLWS(iter.value()).as_string();
bool is_token = net::HttpUtil::IsToken(token_value);
if (iter.name() == kOriginPolicyPolicy) {
valid &= is_token && !policy.has_value();
policy = token_value;
} else if (iter.name() == kOriginPolicyReportTo) {
valid &= is_token && !report_to.has_value();
report_to = token_value;
}
}
valid &= iter.valid();
valid &= (policy.has_value() && policy->find('.') == std::string::npos);
if (!valid)
return OriginPolicyHeaderValues({"", "", header_value});
return OriginPolicyHeaderValues(
{policy.value(), report_to.value_or(""), header_value});
}
// static
void OriginPolicyManager::InvokeCallbackWithPolicyState(
const url::Origin& origin,
OriginPolicyState state,
RetrieveOriginPolicyCallback callback) {
OriginPolicy result;
result.state = state;
result.policy_url = OriginPolicyFetcher::GetDefaultPolicyURL(origin);
std::move(callback).Run(result);
}
} // namespace network