Skip to content

Commit 1a73beb

Browse files
committed
Report stale Domain Access grants rather than publishing through them.
Testing three domains against live projects turned up a failure state the routing work cannot see. Domain Access decides which domain serves which node through node grants, and enabling it leaves Drupal's grants stale until someone rebuilds. Until then every domain serves every page, so a seed collects all of it and publishes one client's content into another client's project. Every page reaches the correct project for the domain being seeded, so the routing is right and nothing reports a problem. Found the hard way: the first live seed of clienta pushed all 63 nodes into static-test-a. After node_access_rebuild() the same seed collected 23, which is clienta's 20 plus the three unrestricted originals. So the seed does respect domain access once the grants are current — content scoping works, it just cannot be assumed. Reported in two places rather than fixed silently. A warning on the status report, and an error on quant:seed-queue, which is where the damage happens and where someone is watching. Both name the consequence rather than the mechanism. Deliberately not rebuilding grants from an update hook. It rebuilds every node's grants, which on a large site holds a deploy open for a long time, and it would run for every site with Domain enabled including those whose grants are fine. Node grants belong to Domain Access, not here. Deliberately not refusing to publish either, unlike the unrecognised host guard. An unrecognised host has no legitimate reading; a pending rebuild does — the flag can be set while content is scoped correctly, and it stays set until an administrator acts, which on a large site can be a while. Only reported where it can do harm: domain_access enabled and two or more domains configured.
1 parent a56a1cb commit 1a73beb

4 files changed

Lines changed: 100 additions & 0 deletions

File tree

quant.install

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,36 @@
66
*/
77

88
use Drupal\Core\Database\Database;
9+
use Drupal\quant\PublishGuard;
910
use Drupal\views\Entity\View;
1011
use Symfony\Component\Yaml\Yaml;
1112

13+
/**
14+
* Implements hook_requirements().
15+
*/
16+
function quant_requirements($phase) {
17+
$requirements = [];
18+
19+
if ($phase !== 'runtime') {
20+
return $requirements;
21+
}
22+
23+
if (!PublishGuard::nodeGrantsAreStale()) {
24+
return $requirements;
25+
}
26+
27+
$requirements['quant_node_grants'] = [
28+
'title' => t('Quant content scoping'),
29+
'value' => t('Node grants need rebuilding'),
30+
'severity' => REQUIREMENT_WARNING,
31+
'description' => t('Domain Access decides which domain serves which content, and its grants are out of date. Until they are rebuilt every domain serves every page, so a Quant seed publishes one site\'s content into another site\'s project. The pages reach the right project for the domain that was seeded, so nothing reports an error. Rebuild permissions on the <a href=":url">status report</a>, then re-seed each domain.', [
32+
':url' => '/admin/reports/status',
33+
]),
34+
];
35+
36+
return $requirements;
37+
}
38+
1239
/**
1340
* Perform setup tasks for Quant.
1441
*/

src/Commands/QuantDrushCommands.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Drush\Drush;
77
use Drupal\Core\Form\FormState;
88
use Drupal\quant\CliDomainContext;
9+
use Drupal\quant\PublishGuard;
910
use Drupal\quant\Seed;
1011
use Drupal\quant\Event\CollectEntitiesEvent;
1112
use Drupal\quant\Event\CollectFilesEvent;
@@ -211,6 +212,13 @@ public function prepare($options = ['reset' => 'true']) {
211212
$this->output()->writeln("Active domain: {$domainId}. Target project: {$project}.");
212213
}
213214

215+
// Stale node grants make every domain serve every page, so this seed
216+
// would collect another client's content and publish it here. It is
217+
// routed correctly, so nothing downstream reports a problem.
218+
if (PublishGuard::nodeGrantsAreStale()) {
219+
$this->output()->writeln('<error>Domain Access grants are out of date. Every domain currently serves every page, so this seed will publish other sites\' content into this project. Rebuild permissions at /admin/reports/status first.</error>');
220+
}
221+
214222
$config = \Drupal::config('quant.settings');
215223

216224
$queue_factory = QuantQueueFactory::getInstance();

src/PublishGuard.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,49 @@ public static function logRefusal(string $what, ?string $host) : void {
9797
]);
9898
}
9999

100+
/**
101+
* Determines whether node grants are stale on a multi-domain site.
102+
*
103+
* Domain Access decides which domain serves which node, through node
104+
* grants.
105+
* Enabling it sets Drupal's needs-rebuild flag, and until someone rebuilds,
106+
* every node is visible on every domain. A seed then collects all of it and
107+
* publishes one client's pages into another client's project — correctly
108+
* routed, but scoped wrongly, so nothing here notices.
109+
*
110+
* Reported rather than enforced. The flag means grants may be stale, not
111+
* that they are, and it stays set until an administrator acts, which on a
112+
* large site can be a while. Refusing to publish for that whole window
113+
* would be worse than saying so loudly.
114+
*
115+
* @return bool
116+
* TRUE when a rebuild is outstanding and it could mis-scope content.
117+
*/
118+
public static function nodeGrantsAreStale() : bool {
119+
if (!\Drupal::hasContainer() || !\Drupal::hasService('module_handler')) {
120+
return FALSE;
121+
}
122+
123+
$moduleHandler = \Drupal::moduleHandler();
124+
125+
if (!$moduleHandler->moduleExists('domain_access') || !$moduleHandler->moduleExists('node')) {
126+
return FALSE;
127+
}
128+
129+
// With one domain there is nowhere else for content to go.
130+
if (!\Drupal::hasService('entity_type.manager')) {
131+
return FALSE;
132+
}
133+
134+
$domains = \Drupal::entityTypeManager()->getStorage('domain')->loadMultiple();
135+
136+
if (count($domains) < 2) {
137+
return FALSE;
138+
}
139+
140+
$moduleHandler->loadInclude('node', 'module');
141+
142+
return function_exists('node_access_needs_rebuild') && node_access_needs_rebuild();
143+
}
144+
100145
}

tests/src/Kernel/PublishGuardWriteTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,24 @@ public function testReadMethodsAreNotGuarded() {
116116
}
117117
}
118118

119+
/**
120+
* Stale node grants are only reported where they can mis-scope content.
121+
*
122+
* Without domain_access there is nothing deciding which domain serves what,
123+
* so a pending rebuild cannot send one client's pages to another. The
124+
* positive case needs domain_access and two domains, and is covered by the
125+
* end-to-end harness rather than here.
126+
*
127+
* @covers ::nodeGrantsAreStale
128+
*/
129+
public function testGrantsCheckIsQuietWithoutDomainAccess() {
130+
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('domain_access'));
131+
132+
// True in core terms, but harmless without per-domain content.
133+
\Drupal::moduleHandler()->loadInclude('node', 'module');
134+
node_access_needs_rebuild(TRUE);
135+
136+
$this->assertFalse(PublishGuard::nodeGrantsAreStale());
137+
}
138+
119139
}

0 commit comments

Comments
 (0)