blob: f69f44249b05d8e13a2cd417115881238a45bf55 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CHECKER_H_
#define CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CHECKER_H_
#include "content/common/content_export.h"
#include <list>
#include <map>
#include <memory>
#include <tuple>
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "content/browser/interest_group/interest_group_permissions_cache.h"
#include "net/base/network_isolation_key.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/origin.h"
namespace network {
class SimpleURLLoader;
}
namespace content {
// Responsible for checking whether a frame can join or leave an interest group.
// Performs .well-known fetches if necessary.
//
// Groups permissions checks that can be handled by the same .well-known fetch,
// to avoid redundant fetches.
//
// TODO(crbug.com/40221941):
// * Consider adding a per-NIK / per-page / per-frame LRU cache. Roundtrip to
// the HTTP cache are slow, and we'll likely want to limit the number of
// pending operations the renderer sends to the browser process at a time.
// * Add detailed error information to DevTools or as a Promise failure on
// rejection.
// * Figure out integration with IsInterestGroupAPIAllowed() - e.g., for
// cross-origin iframes, there are 3 origins (top-level frame, iframe,
// interest group frame). Currently we're not considering iframe origin at
// all. Should we be?
class CONTENT_EXPORT InterestGroupPermissionsChecker {
public:
// More than enough space, since current spec is a dictionary of at most two
// entries, and each entry has keys and values from a fixed set, which have
// limited lengths.
static constexpr int kMaxBodySize = 8192;
static const base::TimeDelta kRequestTimeout;
enum class Operation {
kJoin,
kLeave,
};
using PermissionsCheckCallback =
base::OnceCallback<void(bool operation_allowed)>;
InterestGroupPermissionsChecker();
InterestGroupPermissionsChecker(InterestGroupPermissionsChecker&) = delete;
InterestGroupPermissionsChecker& operator=(InterestGroupPermissionsChecker&) =
delete;
~InterestGroupPermissionsChecker();
// Checks if `frame_origin` can perform `operation` on an interest group owned
// by `interest_group_owner`. Fetches a .well-known URL using
// `url_loader_factory` if needed to check permissions. Has no rate limiting,
// so a fetch will always be started immediately, if one is needed.
//
// Invokes `permissions_check_callback` with the result of the check. May
// invoke callback synchronously, in cases where no .well-known fetch is
// needed.
//
// `permissions_check_callback` may not call CheckPermissions() or delete
// `this` when invoked.
//
// Performs no validity checks on origins. It's up to the caller to make sure
// they're HTTPS, and provided origins are in general allowed to join/leave
// interest groups.
void CheckPermissions(Operation operation,
const url::Origin& frame_origin,
const url::Origin& interest_group_owner,
const net::NetworkIsolationKey& network_isolation_key,
network::mojom::URLLoaderFactory& url_loader_factory,
PermissionsCheckCallback permissions_check_callback);
void ClearCache();
InterestGroupPermissionsCache& cache_for_testing() { return cache_; }
private:
using Permissions = InterestGroupPermissionsCache::Permissions;
// Two permissions checks with the same key can use the same .well-known
// response, though they may have a different associated Operation.
struct PermissionsKey {
bool operator<(const PermissionsKey& other) const {
return std::tie(frame_origin, interest_group_owner,
network_isolation_key) <
std::tie(other.frame_origin, other.interest_group_owner,
other.network_isolation_key);
}
url::Origin frame_origin;
url::Origin interest_group_owner;
net::NetworkIsolationKey network_isolation_key;
};
// A permissions check waiting on a .well-known fetch to complete.
struct PendingPermissionsCheck {
PendingPermissionsCheck(
Operation operation,
PermissionsCheckCallback permissions_check_callback);
PendingPermissionsCheck(PendingPermissionsCheck&&);
~PendingPermissionsCheck();
Operation operation;
PermissionsCheckCallback permissions_check_callback;
};
// A request for a .well-known file, along with a list of
// PendingPermissionChecks that are waiting on it to complete. Deleted once
// `simple_url_loader` has received a response, and its JSON has been parsed
// by an async DataDecoder::ParseJsonIsolated() call.
struct ActiveRequest {
ActiveRequest();
~ActiveRequest();
std::list<PendingPermissionsCheck> pending_checks;
// Used to fetch the .well-known URL.
std::unique_ptr<network::SimpleURLLoader> simple_url_loader;
};
// A map of interest group origins to their ActiveRequests.
using ActiveRequestMap =
std::map<PermissionsKey, std::unique_ptr<ActiveRequest>>;
// Invoked with the result of the ".well-known" fetch for `active_request`.
void OnRequestComplete(ActiveRequestMap::iterator active_request,
std::unique_ptr<std::string> response_body);
// Invoked with the result of parsing the response body associated with
// `active_request` as JSON.
void OnJsonParsed(ActiveRequestMap::iterator active_request,
data_decoder::DataDecoder::ValueOrError result);
// Invoked once the Permissions to use for `active_request` have been
// determined, either as result of an error, by successfully parsing the
// result of a .well-known request as JSON.
void OnActiveRequestComplete(ActiveRequestMap::iterator active_request,
Permissions permissions);
// Returns true if `permissions` allows `operation`.
static bool AllowsOperation(Permissions permissions, Operation operation);
ActiveRequestMap active_requests_;
InterestGroupPermissionsCache cache_;
base::WeakPtrFactory<InterestGroupPermissionsChecker> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CHECKER_H_