Commit 034bb9ca authored by arthursonzogni's avatar arthursonzogni Committed by Commit Bot

[COOP] access reporting: Add body.initialPopupURL

The [coop-reporting-explainer] defined several URLs that might be
reported:
- initial_popup_url
- openee_url
- opener_url
- other_document_url
- referrer

This patch adds body.initialPopupURL

[coop-reporting-explainer]:
https://github.com/camillelamy/explainers/blob/master/coop_reporting.md

Bug: 1090273
Change-Id: I535e44e66578fc90f449be59586f19b154677caf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2424092
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: default avatarCamille Lamy <clamy@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812588}
parent d304ade7
......@@ -24,6 +24,7 @@ namespace {
constexpr char kColumnNumber[] = "columnNumber";
constexpr char kDisposition[] = "disposition";
constexpr char kEffectivePolicy[] = "effectivePolicy";
constexpr char kInitialPopupURL[] = "initialPopupURL";
constexpr char kLineNumber[] = "lineNumber";
constexpr char kNextURL[] = "nextResponseURL";
constexpr char kOpeneeURL[] = "openeeURL";
......@@ -127,6 +128,30 @@ std::string SanitizedURL(const GURL& url) {
return url.GetAsReferrer().spec();
}
class Receiver : public network::mojom::CrossOriginOpenerPolicyReporter {
public:
Receiver(content::CrossOriginOpenerPolicyReporter* reporter,
std::string initial_popup_url)
: reporter_(reporter), initial_popup_url_(initial_popup_url) {}
~Receiver() final = default;
Receiver(const Receiver&) = delete;
Receiver& operator=(const Receiver&) = delete;
private:
void QueueAccessReport(network::mojom::CoopAccessReportType report_type,
const std::string& property,
network::mojom::SourceLocationPtr source_location,
const std::string& reported_window_url) final {
reporter_->QueueAccessReport(report_type, property,
std::move(source_location),
reported_window_url, initial_popup_url_);
}
// |reporter_| is always valid, because it owns |this|.
const content::CrossOriginOpenerPolicyReporter* reporter_;
const std::string initial_popup_url_;
};
} // namespace
CrossOriginOpenerPolicyReporter::CrossOriginOpenerPolicyReporter(
......@@ -185,7 +210,8 @@ void CrossOriginOpenerPolicyReporter::QueueAccessReport(
network::mojom::CoopAccessReportType report_type,
const std::string& property,
network::mojom::SourceLocationPtr source_location,
const std::string& reported_window_url) {
const std::string& reported_window_url,
const std::string& initial_popup_url) const {
// Cross-Origin-Opener-Policy-Report-Only is not required to provide
// endpoints.
if (!coop_.report_only_reporting_endpoint)
......@@ -221,7 +247,7 @@ void CrossOriginOpenerPolicyReporter::QueueAccessReport(
case network::mojom::CoopAccessReportType::kAccessFromCoopPageToOpenee:
case network::mojom::CoopAccessReportType::kAccessToCoopPageFromOpenee:
body.SetStringPath(kOpeneeURL, reported_window_url);
// TODO(arthursonzogni): Fill body.initial_popup_url.
body.SetStringPath(kInitialPopupURL, initial_popup_url);
break;
// Other:
......@@ -235,12 +261,6 @@ void CrossOriginOpenerPolicyReporter::QueueAccessReport(
"coop", endpoint, context_url_, base::nullopt, std::move(body));
}
void CrossOriginOpenerPolicyReporter::Clone(
mojo::PendingReceiver<network::mojom::CrossOriginOpenerPolicyReporter>
receiver) {
receiver_set_.Add(this, std::move(receiver));
}
// static
void CrossOriginOpenerPolicyReporter::InstallAccessMonitorsIfNeeded(
FrameTreeNode* frame) {
......@@ -312,10 +332,6 @@ void CrossOriginOpenerPolicyReporter::MonitorAccesses(
if (!accessed_window_token)
return;
mojo::PendingRemote<network::mojom::CrossOriginOpenerPolicyReporter>
remote_reporter;
Clone(remote_reporter.InitWithNewPipeAndPassReceiver());
bool access_from_coop_page =
this == accessing_node->current_frame_host()->coop_reporter();
......@@ -341,12 +357,33 @@ void CrossOriginOpenerPolicyReporter::MonitorAccesses(
accessed_rfh->GetLastCommittedOrigin());
RenderFrameHostImpl* reported_rfh =
access_from_coop_page ? accessed_rfh : accessing_rfh;
RenderFrameHostImpl* reporting_rfh =
access_from_coop_page ? accessing_rfh : accessed_rfh;
std::string reported_window_url =
same_origin ? SanitizedURL(reported_rfh->GetLastCommittedURL()) : "";
bool endpoint_defined =
coop_.report_only_reporting_endpoint || coop_.reporting_endpoint;
// If the COOP window is the opener, and the other window's popup creator is
// same-origin with the COOP document, the openee' initial popup URL is
// reported.
std::string reported_initial_popup_url;
if (report_type == CoopAccessReportType::kAccessFromCoopPageToOpenee ||
report_type == CoopAccessReportType::kAccessToCoopPageFromOpenee) {
if (reporting_rfh->GetLastCommittedOrigin().IsSameOriginWith(
reported_rfh->frame_tree_node()->popup_creator_origin())) {
reported_initial_popup_url =
SanitizedURL(reported_rfh->frame_tree_node()->initial_popup_url());
}
}
mojo::PendingRemote<network::mojom::CrossOriginOpenerPolicyReporter>
remote_reporter;
receiver_set_.Add(
std::make_unique<Receiver>(this, reported_initial_popup_url),
remote_reporter.InitWithNewPipeAndPassReceiver());
// Warning: Do not send cross-origin sensitive data. They will be read from:
// 1) A potentially compromised renderer (the accessing window).
// 2) A network server (defined from the reporter).
......
......@@ -10,7 +10,7 @@
#include "base/optional.h"
#include "content/common/content_export.h"
#include "content/public/browser/global_routing_id.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/unique_receiver_set.h"
#include "services/network/public/mojom/cross_origin_opener_policy.mojom.h"
#include "services/network/public/mojom/source_location.mojom-forward.h"
#include "url/gurl.h"
......@@ -29,14 +29,13 @@ class RenderFrameHostImpl;
// pass the receiver to other processes.
// Any functions other than the destructor must not be called after the
// associated StoragePartition is destructed.
class CONTENT_EXPORT CrossOriginOpenerPolicyReporter final
: public network::mojom::CrossOriginOpenerPolicyReporter {
class CONTENT_EXPORT CrossOriginOpenerPolicyReporter {
public:
CrossOriginOpenerPolicyReporter(StoragePartition* storage_partition,
const GURL& context_url,
const GURL& context_referrer_url,
const network::CrossOriginOpenerPolicy& coop);
~CrossOriginOpenerPolicyReporter() override;
~CrossOriginOpenerPolicyReporter();
CrossOriginOpenerPolicyReporter(const CrossOriginOpenerPolicyReporter&) =
delete;
CrossOriginOpenerPolicyReporter& operator=(
......@@ -46,11 +45,11 @@ class CONTENT_EXPORT CrossOriginOpenerPolicyReporter final
storage_partition_ = storage_partition;
}
// network::mojom::CrossOriginOpenerPolicyReporter implementation.
void QueueAccessReport(network::mojom::CoopAccessReportType report_type,
const std::string& property,
network::mojom::SourceLocationPtr source_location,
const std::string& reported_window_url) final;
const std::string& reported_window_url,
const std::string& initial_popup_url) const;
// Sends reports when COOP causing a browsing context group switch that
// breaks opener relationships.
......@@ -67,10 +66,6 @@ class CONTENT_EXPORT CrossOriginOpenerPolicyReporter final
// CoopAccessMonitor. The first window is identified by |node|.
static void InstallAccessMonitorsIfNeeded(FrameTreeNode* node);
void Clone(
mojo::PendingReceiver<network::mojom::CrossOriginOpenerPolicyReporter>
receiver) override;
// Generate a new, previously unused, virtualBrowsingContextId.
static int NextVirtualBrowsingContextGroup();
......@@ -92,7 +87,7 @@ class CONTENT_EXPORT CrossOriginOpenerPolicyReporter final
const std::string context_referrer_url_;
const network::CrossOriginOpenerPolicy coop_;
mojo::ReceiverSet<network::mojom::CrossOriginOpenerPolicyReporter>
mojo::UniqueReceiverSet<network::mojom::CrossOriginOpenerPolicyReporter>
receiver_set_;
};
......
......@@ -136,31 +136,4 @@ TEST_F(CrossOriginOpenerPolicyReporterTest, UserAndPassSanitization) {
"https://www2.example.com/x");
}
TEST_F(CrossOriginOpenerPolicyReporterTest, Clone) {
auto reporter = GetReporter();
std::string url1 = "https://www1.example.com/y?bar=baz#foo";
std::string url1_sanitized = "https://www1.example.com/y?bar=baz";
mojo::Remote<network::mojom::CrossOriginOpenerPolicyReporter> remote;
reporter->Clone(remote.BindNewPipeAndPassReceiver());
reporter->QueueNavigationToCOOPReport(GURL(url1), true, false);
remote.FlushForTesting();
ASSERT_EQ(1u, network_context().reports().size());
const Report& r1 = network_context().reports()[0];
EXPECT_EQ(r1.type, "coop");
EXPECT_EQ(r1.url, context_url());
EXPECT_EQ(r1.body.FindKey("disposition")->GetString(), "enforce");
EXPECT_EQ(r1.body.FindKey("previousResponseURL")->GetString(),
url1_sanitized);
EXPECT_EQ(r1.body.FindKey("referrer")->GetString(),
"https://referrer.com/?a");
EXPECT_EQ(r1.body.FindKey("type")->GetString(), "navigation-to-response");
EXPECT_EQ(r1.body.FindKey("effectivePolicy")->GetString(),
"same-origin-plus-coep");
}
} // namespace content
......@@ -763,4 +763,16 @@ void FrameTreeNode::SetAdFrameType(blink::mojom::AdFrameType ad_frame_type) {
}
}
void FrameTreeNode::SetInitialPopupURL(const GURL& initial_popup_url) {
DCHECK(initial_popup_url_.is_empty());
DCHECK(!has_committed_real_load_);
initial_popup_url_ = initial_popup_url;
}
void FrameTreeNode::SetPopupCreatorOrigin(
const url::Origin& popup_creator_origin) {
DCHECK(!has_committed_real_load_);
popup_creator_origin_ = popup_creator_origin;
}
} // namespace content
......@@ -427,6 +427,25 @@ class CONTENT_EXPORT FrameTreeNode {
void SetAdFrameType(blink::mojom::AdFrameType ad_frame_type);
// The initial popup URL for new window opened using:
// `window.open(initial_popup_url)`.
// An empty GURL otherwise.
//
// [WARNING] There is no guarantee the FrameTreeNode will ever host a
// document served from this URL. The FrameTreeNode always starts hosting the
// initial empty document and attempts a navigation toward this URL. However
// the navigation might be delayed, redirected and even cancelled.
void SetInitialPopupURL(const GURL& initial_popup_url);
const GURL& initial_popup_url() const { return initial_popup_url_; }
// The origin of the document that used window.open() to create this frame.
// Otherwise, an opaque Origin with a nonce different from all previously
// existing Origins.
void SetPopupCreatorOrigin(const url::Origin& popup_creator_origin);
const url::Origin& popup_creator_origin() const {
return popup_creator_origin_;
}
private:
FRIEND_TEST_ALL_PREFIXES(SitePerProcessFeaturePolicyBrowserTest,
ContainerPolicyDynamic);
......@@ -496,6 +515,14 @@ class CONTENT_EXPORT FrameTreeNode {
// destroyed.
std::unique_ptr<OpenerDestroyedObserver> original_opener_observer_;
// When created by an opener, the URL specified in window.open(url)
// Please refer to {Get,Set}InitialPopupURL() documentation.
GURL initial_popup_url_;
// When created using window.open, the origin of the creator.
// Please refer to {Get,Set}PopupCreatorOrigin() documentation.
url::Origin popup_creator_origin_;
// Whether this frame has committed any real load, replacing its initial
// about:blank page.
bool has_committed_real_load_;
......
......@@ -1028,6 +1028,9 @@ std::unique_ptr<WebContentsImpl> WebContentsImpl::CreateWithOpener(
new_root->SetOpenerFeaturePolicyState(
opener_rfh->feature_policy()->GetFeatureState());
}
new_root->SetInitialPopupURL(params.initial_popup_url);
new_root->SetPopupCreatorOrigin(opener_rfh->GetLastCommittedOrigin());
}
// Apply starting sandbox flags.
......@@ -3632,6 +3635,7 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
create_params.opener_render_frame_id = opener->GetRoutingID();
create_params.opener_suppressed = params.opener_suppressed;
create_params.initially_hidden = renderer_started_hidden;
create_params.initial_popup_url = params.target_url;
// Even though all codepaths leading here are in response to a renderer
// tryng to open a new window, if the new window ends up in a different
......
......@@ -151,6 +151,12 @@ class WebContents : public PageNavigator,
// window.open('', 'bar')).
std::string main_frame_name;
// New window starts from the initial empty document. When created by an
// opener, the latter can request an initial navigation attempt to be made.
// This is the url specified in: `window.open(initial_popup_url, ...)`.
// This is empty otherwise.
GURL initial_popup_url;
// True if the contents should be initially hidden.
bool initially_hidden;
......
......@@ -18,8 +18,6 @@ enum CoopAccessReportType {
};
// Reports potential COOP violations. Implemented in the browser process.
// TODO(ahemery, pmeuleman): Add extra coop breakage cases as listed in
// https://docs.google.com/document/d/1zWqwI8PFrezwQpBSejIMUfdtsIYl9-h8epasdrDXVIM/edit
interface CrossOriginOpenerPolicyReporter {
// Sends a report when two browsing contexts from different virtual browsing
// context groups tries to access each other.
......@@ -30,9 +28,6 @@ interface CrossOriginOpenerPolicyReporter {
QueueAccessReport(CoopAccessReportType report_type, string property,
SourceLocation source_location,
string reported_window_url);
// Connects a new pipe to this instance.
Clone(pending_receiver<CrossOriginOpenerPolicyReporter> receiver);
};
// Cross-Origin-Opener-Policy enum representing parsed values.
......
......@@ -73,6 +73,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, openee_url);
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, openee_requested_url);
}, name);
runTest(false, "access-from-coop-page-to-openee, same-origin");
......
......@@ -74,6 +74,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, "");
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, openee_requested_url);
}, name);
runTest(false, "access-from-coop-page-to-openee, cross-origin");
......
......@@ -52,6 +52,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, opener_url);
assert_equals(report.body.initialPopupURL, undefined);
}, name);
runTest(false, "access-from-coop-page-to-opener, same-origin");
......
......@@ -53,6 +53,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, opener_url);
assert_equals(report.body.initialPopupURL, undefined);
}, name);
runTest(false, "access-from-coop-page-to-opener, cross-origin");
......
......@@ -85,6 +85,7 @@ promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, other_url.replace(/"/g, '%22'));
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, undefined);
}, "access-from-coop-page-to-other (COOP-RO)");
</script>
......@@ -67,6 +67,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, openee_url);
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, openee_requested_url);
}, name);
runTest(false, "access-to-coop-page-from-openee, same-origin");
......
......@@ -68,6 +68,8 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, "");
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, openee_requested_url);
}, name);
runTest(false, "access-to-coop-page-from-openee, cross-origin");
......
......@@ -59,6 +59,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, opener_url);
assert_equals(report.body.initialPopupURL, undefined);
}, name);
runTest(false, "access-to-coop-page-from-opener, same-origin");
......
......@@ -60,6 +60,7 @@ let runTest = (openee_redirect, name) => promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, undefined);
assert_equals(report.body.referrer, opener_url);
assert_equals(report.body.initialPopupURL, undefined);
}, name);
runTest(false, "access-to-coop-page-from-opener, cross-origin");
......
......@@ -73,6 +73,7 @@ promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, other_url);
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, undefined);
}, "access-to-coop-page-from-other (COOP-RO)");
</script>
......@@ -74,6 +74,7 @@ promise_test(async t => {
assert_equals(report.body.openeeURL, undefined);
assert_equals(report.body.otherDocumentURL, "");
assert_equals(report.body.referrer, undefined);
assert_equals(report.body.initialPopupURL, undefined);
}, "access-to-coop-page-from-other (COOP-RO)");
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment