From 08b493a20287bf8466ccb9288cb17602e6bc7ee9 Mon Sep 17 00:00:00 2001
From: Dave Long <dave@longwaveconsulting.com>
Date: Tue, 17 Oct 2023 12:42:37 +0200
Subject: [PATCH] Issue #3205688 by jedihe, mr.baileys, dpagini: Include
 allowedOriginsPatterns in default.services.yml (regex matching for CORS)

---
 .../scaffold/files/default.services.yml       |  2 +
 .../HttpKernel/CorsIntegrationTest.php        | 47 +++++++++++++++++++
 sites/default/default.services.yml            |  2 +
 3 files changed, 51 insertions(+)

diff --git a/core/assets/scaffold/files/default.services.yml b/core/assets/scaffold/files/default.services.yml
index 8a6cdf2f77fa..c4b964fc2900 100644
--- a/core/assets/scaffold/files/default.services.yml
+++ b/core/assets/scaffold/files/default.services.yml
@@ -214,6 +214,8 @@ parameters:
     # Configure requests allowed from specific origins. Do not include trailing
     # slashes with URLs.
     allowedOrigins: ['*']
+    # Configure requests allowed from origins, matching against regex patterns.
+    allowedOriginsPatterns: []
     # Sets the Access-Control-Expose-Headers header.
     exposedHeaders: false
     # Sets the Access-Control-Max-Age header.
diff --git a/core/tests/Drupal/FunctionalTests/HttpKernel/CorsIntegrationTest.php b/core/tests/Drupal/FunctionalTests/HttpKernel/CorsIntegrationTest.php
index ac21766b0d2d..99ea4944ea36 100644
--- a/core/tests/Drupal/FunctionalTests/HttpKernel/CorsIntegrationTest.php
+++ b/core/tests/Drupal/FunctionalTests/HttpKernel/CorsIntegrationTest.php
@@ -65,8 +65,55 @@ public function testCrossSiteRequest() {
     $this->assertSession()->responseHeaderEquals('Access-Control-Allow-Origin', '*');
     $this->assertSession()->responseHeaderNotContains('Vary', 'Origin');
 
+    // Configure the CORS stack to match allowed origins using regex patterns.
+    $cors_config['allowedOrigins'] = [];
+    $cors_config['allowedOriginsPatterns'] = ['#^http://[a-z-]*\.valid.com$#'];
+
+    $this->setContainerParameter('cors.config', $cors_config);
+    $this->rebuildContainer();
+
+    // Fire a request from an origin that isn't allowed.
+    /** @var \Symfony\Component\HttpFoundation\Response $response */
+    $this->drupalGet('/test-page', [], ['Origin' => 'http://non-valid.com']);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderDoesNotExist('Access-Control-Allow-Origin');
+    $this->assertSession()->responseHeaderContains('Vary', 'Origin');
+
+    // Specify a valid origin.
+    $this->drupalGet('/test-page', [], ['Origin' => 'http://sub-domain.valid.com']);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderEquals('Access-Control-Allow-Origin', 'http://sub-domain.valid.com');
+    $this->assertSession()->responseHeaderContains('Vary', 'Origin');
+
+    // Test combining allowedOrigins and allowedOriginsPatterns.
+    $cors_config['allowedOrigins'] = ['http://domainA.com'];
+    $cors_config['allowedOriginsPatterns'] = ['#^http://domain[B-Z-]*\.com$#'];
+
+    $this->setContainerParameter('cors.config', $cors_config);
+    $this->rebuildContainer();
+
+    // Specify an origin that does not match allowedOrigins nor
+    // allowedOriginsPattern.
+    $this->drupalGet('/test-page', [], ['Origin' => 'http://non-valid.com']);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderDoesNotExist('Access-Control-Allow-Origin');
+    $this->assertSession()->responseHeaderContains('Vary', 'Origin');
+
+    // Specify a valid origin that matches allowedOrigins.
+    $this->drupalGet('/test-page', [], ['Origin' => 'http://domainA.com']);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderEquals('Access-Control-Allow-Origin', 'http://domainA.com');
+    $this->assertSession()->responseHeaderContains('Vary', 'Origin');
+
+    // Specify a valid origin that matches allowedOriginsPatterns.
+    $this->drupalGet('/test-page', [], ['Origin' => 'http://domainX.com']);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderEquals('Access-Control-Allow-Origin', 'http://domainX.com');
+    $this->assertSession()->responseHeaderContains('Vary', 'Origin');
+
     // Configure the CORS stack to allow a specific origin.
     $cors_config['allowedOrigins'] = ['http://example.com'];
+    $cors_config['allowedOriginsPatterns'] = [];
 
     $this->setContainerParameter('cors.config', $cors_config);
     $this->rebuildContainer();
diff --git a/sites/default/default.services.yml b/sites/default/default.services.yml
index 8a6cdf2f77fa..c4b964fc2900 100644
--- a/sites/default/default.services.yml
+++ b/sites/default/default.services.yml
@@ -214,6 +214,8 @@ parameters:
     # Configure requests allowed from specific origins. Do not include trailing
     # slashes with URLs.
     allowedOrigins: ['*']
+    # Configure requests allowed from origins, matching against regex patterns.
+    allowedOriginsPatterns: []
     # Sets the Access-Control-Expose-Headers header.
     exposedHeaders: false
     # Sets the Access-Control-Max-Age header.
-- 
GitLab