From 573e752f043912e74cfa48508f6efc15725427c1 Mon Sep 17 00:00:00 2001
From: effulgentsia <alex.bronstein@acquia.com>
Date: Fri, 14 May 2021 13:39:12 -0700
Subject: [PATCH] Issue #3209628 by longwave, rachel_norfolk, antiorario,
 phenaproxima, rootwork, larowlan, neclimdul, Maeglin: Add Permissions-Policy
 header to block Google FLoC

---
 .../scaffold/files/default.settings.php       | 15 ++++
 .../FinishResponseSubscriber.php              | 14 +++
 .../Core/Http/BlockInterestCohortTest.php     | 85 +++++++++++++++++++
 sites/default/default.settings.php            | 15 ++++
 4 files changed, 129 insertions(+)
 create mode 100644 core/tests/Drupal/KernelTests/Core/Http/BlockInterestCohortTest.php

diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php
index 0165492a545e..f02935a27abc 100644
--- a/core/assets/scaffold/files/default.settings.php
+++ b/core/assets/scaffold/files/default.settings.php
@@ -611,6 +611,21 @@
 # ini_set('pcre.backtrack_limit', 200000);
 # ini_set('pcre.recursion_limit', 200000);
 
+/**
+ * Add Permissions-Policy header to disable Google FLoC.
+ *
+ * By default, Drupal sends the 'Permissions-Policy: interest-cohort=()' header
+ * to disable Google's Federated Learning of Cohorts feature, introduced in
+ * Chrome 89.
+ *
+ * See https://en.wikipedia.org/wiki/Federated_Learning_of_Cohorts for more
+ * information about FLoC.
+ *
+ * If you don't wish to disable FLoC in Chrome, you can set this value
+ * to FALSE.
+ */
+# $settings['block_interest_cohort'] = TRUE;
+
 /**
  * Configuration overrides.
  *
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index c8d76eb294dc..d5816315a1cb 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -131,6 +131,20 @@ public function onRespond(ResponseEvent $event) {
     $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE);
     $response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE);
 
+    // Add a Permissions-Policy header to block Federated Learning of Cohorts.
+    if (Settings::get('block_interest_cohort', TRUE)) {
+      if (!$response->headers->has('Permissions-Policy')) {
+        $response->headers->set('Permissions-Policy', 'interest-cohort=()');
+      }
+      else {
+        // Only add interest-cohort if the header does not contain it already.
+        $permissions_policy = $response->headers->get('Permissions-Policy');
+        if (strpos($permissions_policy, 'interest-cohort') === FALSE) {
+          $response->headers->set('Permissions-Policy', $permissions_policy . ', interest-cohort=()');
+        }
+      }
+    }
+
     // If the current response isn't an implementation of the
     // CacheableResponseInterface, we assume that a Response is either
     // explicitly not cacheable or that caching headers are already set in
diff --git a/core/tests/Drupal/KernelTests/Core/Http/BlockInterestCohortTest.php b/core/tests/Drupal/KernelTests/Core/Http/BlockInterestCohortTest.php
new file mode 100644
index 000000000000..ff87ae470d1c
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Http/BlockInterestCohortTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Http;
+
+use Drupal\Core\Site\Settings;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\ResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Tests the Permissions-Policy header added by FinishResponseSubscriber.
+ *
+ * @group Http
+ */
+class BlockInterestCohortTest extends KernelTestBase {
+
+  /**
+   * Tests that FLoC is blocked by default.
+   */
+  public function testDefaultBlocking() {
+    $request = Request::create('/');
+    $response = \Drupal::service('http_kernel')->handle($request);
+
+    $this->assertSame('interest-cohort=()', $response->headers->get('Permissions-Policy'));
+  }
+
+  /**
+   * Tests that an existing interest-cohort policy is not overwritten.
+   */
+  public function testExistingInterestCohortPolicy() {
+    $headers['Permissions-Policy'] = 'interest-cohort=*';
+
+    $kernel = \Drupal::service('http_kernel');
+    $request = Request::create('/');
+    $response = new Response('', 200, $headers);
+    $event = new ResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
+    \Drupal::service('finish_response_subscriber')->onRespond($event);
+
+    $this->assertSame($headers['Permissions-Policy'], $response->headers->get('Permissions-Policy'));
+  }
+
+  /**
+   * Tests that an existing header is appended to correctly.
+   */
+  public function testExistingPolicyHeader() {
+    $headers['Permissions-Policy'] = 'geolocation=()';
+
+    $kernel = \Drupal::service('http_kernel');
+    $request = Request::create('/');
+    $response = new Response('', 200, $headers);
+    $event = new ResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
+    \Drupal::service('finish_response_subscriber')->onRespond($event);
+
+    $permissions_policy = $response->headers->get('Permissions-Policy');
+    $this->assertStringContainsString('geolocation=()', $permissions_policy);
+    $this->assertStringContainsString('interest-cohort=()', $permissions_policy);
+  }
+
+  /**
+   * Tests that FLoC blocking is ignored for subrequests.
+   */
+  public function testSubrequestBlocking() {
+    $request = Request::create('/');
+    $response = \Drupal::service('http_kernel')->handle($request, HttpKernelInterface::SUB_REQUEST);
+
+    $this->assertFalse($response->headers->has('Permissions-Policy'));
+  }
+
+  /**
+   * Tests that FLoC blocking can be disabled in settings.php.
+   */
+  public function testDisableBlockSetting() {
+    $settings = Settings::getAll();
+    $settings['block_interest_cohort'] = FALSE;
+    new Settings($settings);
+
+    $request = Request::create('/');
+    $response = \Drupal::service('http_kernel')->handle($request);
+
+    $this->assertFalse($response->headers->has('Permissions-Policy'));
+  }
+
+}
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 0165492a545e..f02935a27abc 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -611,6 +611,21 @@
 # ini_set('pcre.backtrack_limit', 200000);
 # ini_set('pcre.recursion_limit', 200000);
 
+/**
+ * Add Permissions-Policy header to disable Google FLoC.
+ *
+ * By default, Drupal sends the 'Permissions-Policy: interest-cohort=()' header
+ * to disable Google's Federated Learning of Cohorts feature, introduced in
+ * Chrome 89.
+ *
+ * See https://en.wikipedia.org/wiki/Federated_Learning_of_Cohorts for more
+ * information about FLoC.
+ *
+ * If you don't wish to disable FLoC in Chrome, you can set this value
+ * to FALSE.
+ */
+# $settings['block_interest_cohort'] = TRUE;
+
 /**
  * Configuration overrides.
  *
-- 
GitLab