From 47d0396100c43d673579bf1f788b7ebccd53b011 Mon Sep 17 00:00:00 2001
From: Wong Hoi Sing Edison <hswong3i@pantarei-design.com>
Date: Sat, 28 Dec 2024 23:03:26 +0800
Subject: [PATCH] Issue #2925890 by BR0kEN, berdir, smustgrave,
 ranjith_kumar_k_u, afsch, jordanpagewhite: Invalid config structures can
 result in exceptions when saving a config entity

---
 core/lib/Drupal/Core/Config/Config.php        | 26 +++++++++++------
 .../Core/Config/ConfigCRUDTest.php            | 29 +++++++++----------
 .../Core/Config/ConfigSchemaTest.php          |  2 +-
 3 files changed, 31 insertions(+), 26 deletions(-)

diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index 447bed82fb80..edff99d4ea09 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -204,18 +204,26 @@ public function save($has_trusted_data = FALSE) {
 
     // If there is a schema for this configuration object, cast all values to
     // conform to the schema.
-    if (!$has_trusted_data) {
-      if ($this->typedConfigManager->hasConfigSchema($this->name)) {
-        // Ensure that the schema wrapper has the latest data.
-        $this->schemaWrapper = NULL;
-        $this->data = $this->castValue(NULL, $this->data);
-      }
-      else {
-        foreach ($this->data as $key => $value) {
-          $this->validateValue($key, $value);
+    try {
+      if (!$has_trusted_data) {
+        if ($this->typedConfigManager->hasConfigSchema($this->name)) {
+          // Ensure that the schema wrapper has the latest data.
+          $this->schemaWrapper = NULL;
+          foreach ($this->data as $key => $value) {
+            $this->data[$key] = $this->castValue($key, $value);
+          }
+        }
+        else {
+          foreach ($this->data as $key => $value) {
+            $this->validateValue($key, $value);
+          }
         }
       }
     }
+    catch (\Exception $e) {
+      // Log and ignore exceptions caused by invalid config structures.
+      watchdog_exception('config', $e);
+    }
 
     // Potentially configuration schema could have changed the underlying data's
     // types.
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigCRUDTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigCRUDTest.php
index 7e7dbd304e77..eeb026edb16e 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigCRUDTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigCRUDTest.php
@@ -9,7 +9,6 @@
 use Drupal\Core\Config\ConfigNameException;
 use Drupal\Core\Config\ConfigValueException;
 use Drupal\Core\Config\DatabaseStorage;
-use Drupal\Core\Config\UnsupportedDataTypeConfigException;
 use Drupal\KernelTests\KernelTestBase;
 
 /**
@@ -262,7 +261,7 @@ public function testValueValidation(): void {
    * Tests data type handling.
    */
   public function testDataTypes(): void {
-    \Drupal::service('module_installer')->install(['config_test']);
+    \Drupal::service('module_installer')->install(['config_test', 'dblog']);
     $storage = new DatabaseStorage($this->container->get('database'), 'config');
     $name = 'config_test.types';
     $config = $this->config($name);
@@ -325,28 +324,26 @@ public function testDataTypes(): void {
 
     // Test that setting an unsupported type for a config object with a schema
     // fails.
-    try {
-      $config->set('stream', fopen(__FILE__, 'r'))->save();
-      $this->fail('No Exception thrown upon saving invalid data type.');
-    }
-    catch (UnsupportedDataTypeConfigException) {
-      // Expected exception; just continue testing.
-    }
+    // does not throw an exception but logs an error.
+    $config->set('stream', fopen(__FILE__, 'r'))->save();
+    $last_watchdog = \Drupal::database()->query('SELECT * FROM {watchdog} ORDER BY wid DESC')->fetchAssoc();
+    $variables = unserialize($last_watchdog['variables']);
+    $this->assertEquals('Invalid data type for config element config_test.types:stream', $variables['@message']);
+    $this->assertEquals('Drupal\Core\Config\UnsupportedDataTypeConfigException', $variables['%type']);
 
     // Test that setting an unsupported type for a config object with no schema
     // also fails.
+    // also logs an error.
     $typed_config_manager = $this->container->get('config.typed');
     $config_name = 'config_test.no_schema';
     $config = $this->config($config_name);
     $this->assertFalse($typed_config_manager->hasConfigSchema($config_name));
 
-    try {
-      $config->set('stream', fopen(__FILE__, 'r'))->save();
-      $this->fail('No Exception thrown upon saving invalid data type.');
-    }
-    catch (UnsupportedDataTypeConfigException) {
-      // Expected exception; just continue testing.
-    }
+    $config->set('stream', fopen(__FILE__, 'r'))->save();
+    $last_watchdog = \Drupal::database()->query('SELECT * FROM {watchdog} ORDER BY wid DESC')->fetchAssoc();
+    $variables = unserialize($last_watchdog['variables']);
+    $this->assertEquals('Invalid data type for config element config_test.no_schema:stream', $variables['@message']);
+    $this->assertEquals('Drupal\Core\Config\UnsupportedDataTypeConfigException', $variables['%type']);
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
index fe47ba596de9..d08844389bc0 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
@@ -485,8 +485,8 @@ public function testConfigSaveWithSchema(): void {
   public function testConfigSaveMappingSort(): void {
     // Top level map sorting.
     $data = [
-      'foo' => '1',
       'bar' => '2',
+      'foo' => '1',
     ];
     // Save config which has a schema that enforces types.
     $this->config('config_schema_test.schema_mapping_sort')
-- 
GitLab