diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 66f51e4..dd5b9bb 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -263,6 +263,9 @@ public function import() { */ public function validate() { if (!$this->validated) { + if (!$this->storageComparer->validateSiteUuid()) { + throw new ConfigImporterException('Site UUID in source storage does not match the target storage.'); + } $this->notify('validate'); $this->validated = TRUE; } diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php index 834f439..c08cb5a 100644 --- a/core/lib/Drupal/Core/Config/StorageComparer.php +++ b/core/lib/Drupal/Core/Config/StorageComparer.php @@ -195,4 +195,13 @@ protected function getTargetNames() { return $this->targetNames; } + /** + * {@inheritdoc} + */ + public function validateSiteUuid() { + $source = $this->sourceStorage->read('system.site'); + $target = $this->targetStorage->read('system.site'); + return $source['uuid'] === $target['uuid']; + } + } diff --git a/core/lib/Drupal/Core/Config/StorageComparerInterface.php b/core/lib/Drupal/Core/Config/StorageComparerInterface.php index 5ca0f3e..0f62d0c 100644 --- a/core/lib/Drupal/Core/Config/StorageComparerInterface.php +++ b/core/lib/Drupal/Core/Config/StorageComparerInterface.php @@ -117,4 +117,12 @@ public function reset(); */ public function hasChanges($ops = array('delete', 'create', 'update')); + /** + * Validates that the system.site::uuid in the source and target match. + * + * @return bool + * TRUE if identical, FALSE if not. + */ + public function validateSiteUuid(); + } diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index 2994283..8e95d7e 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -160,6 +160,11 @@ public function buildForm(array $form, array &$form_state) { $form['actions']['#access'] = FALSE; return $form; } + elseif (!$storage_comparer->validateSiteUuid()) { + drupal_set_message($this->t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.'), 'error'); + $form['actions']['#access'] = FALSE; + return $form; + } else { // Store the comparer for use in the submit. $form_state['storage_comparer'] = $storage_comparer; diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigExportImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigExportImportUITest.php index 539879a..a214b63 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigExportImportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigExportImportUITest.php @@ -21,6 +21,13 @@ class ConfigExportImportUITest extends WebTestBase { /** + * The site UUID. + * + * @var string + */ + protected $siteUuid; + + /** * The slogan value, for a simple export test case. * * @var string @@ -69,6 +76,8 @@ protected function setUp() { * Tests a simple site configuration export case: site slogan. */ function testExport() { + $this->siteUuid = \Drupal::config('system.site')->get('uuid'); + // Create a role for second round. $this->admin_role = $this->drupalCreateRole(array('synchronize configuration', 'import configuration')); $this->slogan = $this->randomString(16); @@ -104,6 +113,9 @@ function testImport() { * The name of the tarball containing the configuration to be imported. */ protected function doImport($filename) { + // The site UUIDs must match for the import to work. + \Drupal::config('system.site')->set('uuid', $this->siteUuid)->save(); + $this->assertNotEqual($this->slogan, \Drupal::config('system.site')->get('slogan')); $this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload'); $this->drupalPostForm(NULL, array(), 'Import all'); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php index 2f4ac16..e495602 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -116,6 +116,23 @@ function testImportLock() { } /** + * Tests verification of site UUID before importing configuration. + */ + function testImportSiteUuidValidation() { + $staging = \Drupal::service('config.storage.staging'); + // Create updated configuration object. + $config_data = \Drupal::config('system.site')->get(); + // Generate a new site UUID. + $config_data['uuid'] = \Drupal::service('uuid')->generate(); + $staging->write('system.site', $config_data); + + // Verify that there are configuration differences to import. + $this->drupalGet('admin/config/development/configuration'); + $this->assertText(t('The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.')); + $this->assertNoFieldById('edit-submit', t('Import all')); + } + + /** * Tests the screen that shows differences between active and staging. */ function testImportDiff() { diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php index 59ea550..6af6295 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php @@ -97,6 +97,25 @@ function testEmptyImportFails() { } /** + * Tests verification of site UUID before importing configuration. + */ + function testSiteUuidValidate() { + $staging = \Drupal::service('config.storage.staging'); + // Create updated configuration object. + $config_data = \Drupal::config('system.site')->get(); + // Generate a new site UUID. + $config_data['uuid'] = \Drupal::service('uuid')->generate(); + $staging->write('system.site', $config_data); + try { + $this->configImporter->reset()->import(); + $this->assertFalse(FALSE, 'ConfigImporterException not thrown, invalid import was not stopped due to mis-matching site UUID.'); + } + catch (ConfigImporterException $e) { + $this->assertEqual($e->getMessage(), 'Site UUID in source storage does not match the target storage.'); + } + } + + /** * Tests deletion of configuration during import. */ function testDeleted() { diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 0dabf5e..9d585f0 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -4,6 +4,9 @@ system.site: type: mapping label: 'Site information' mapping: + uuid: + type: string + label: 'Site UUID' name: type: label label: 'Site name' diff --git a/core/modules/system/config/system.site.yml b/core/modules/system/config/system.site.yml index b1c5df5..4be7ae6 100644 --- a/core/modules/system/config/system.site.yml +++ b/core/modules/system/config/system.site.yml @@ -1,3 +1,4 @@ +uuid: '' name: Drupal mail: '' slogan: '' diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 14153a0..34d8dde 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -565,6 +565,11 @@ function system_install() { // Populate the cron key state variable. $cron_key = Crypt::randomStringHashed(55); \Drupal::state()->set('system.cron_key', $cron_key); + + // Populate the site UUID. + \Drupal::config('system.site') + ->set('uuid', \Drupal::service('uuid')->generate()) + ->save(); } /**