fix(security): unbekannte Requirement-ID sperrt das Divi-Tool

Deklariert ein Tool über DeclaresRequirements eine ID, die keine Divi-Fähigkeit ist, wird der Aufruf mit divi_incompatible (423) gesperrt, statt ungesperrt zu laufen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-06 11:30:05 +01:00
co-authored by Claude Opus 5.5
parent 234fd1edac
commit 691231f776
4 changed files with 85 additions and 11 deletions
@@ -265,14 +265,18 @@ final class DiviFeatureGateTest extends TestCase
);
}
public function testAnUnknownRequirementOfTheCoreIsIgnored(): void
public function testAnUnknownRequirementOfTheCoreLocksTheToolAndIsLogged(): void
{
DiviFeatureFixtures::lock(DiviFeature::PageWrite);
$tool = $this->contentCreate($this->composerNeeding('something-else'));
$log = new FakeChangeLog();
$tool = $this->contentCreate($this->composerNeeding('page-write', 'something-else'));
self::assertNull(
$this->gate(FakeChangeLog::forbidden())->refusal($tool, ['post_type' => 'page', 'divi' => [[]]])
);
$error = $this->gate($log)->refusal($tool, ['post_type' => 'page', 'divi' => [[]]]);
$payload = self::assertToolError($error, 'divi_incompatible');
self::assertSame(['something-else'], $payload['unknown']);
self::assertSame(423, $error->get_error_data()['status'] ?? null);
self::assertSame('lit/content-create', $log->records[0]['ability']);
self::assertSame('refused', $log->records[0]['result']);
}
public function testAnnotateNeverChangesAResult(): void
@@ -108,10 +108,10 @@ final class FeatureSetsTest extends TestCase
self::assertSame([DiviFeature::Roles], FeatureSets::declaredBy($tool, ['a' => 1]));
}
public function testDeclaredByTranslatesTheIdsOfACoreToolAndIgnoresUnknownOnes(): void
public function testDeclaredByTranslatesTheIdsOfACoreTool(): void
{
$tool = Mockery::mock(Ability::class, DeclaresRequirements::class);
$tool->expects('requirements')->with(['divi' => []])->andReturn(['page-write', 'other-integration', 'catalog']);
$tool->expects('requirements')->with(['divi' => []])->andReturn(['page-write', 'catalog']);
self::assertSame(
[DiviFeature::PageWrite, DiviFeature::Catalog],
@@ -119,6 +119,22 @@ final class FeatureSetsTest extends TestCase
);
}
public function testUnknownIdsNamesTheIdsOfACoreToolThatAreNoDiviFeature(): void
{
$tool = Mockery::mock(Ability::class, DeclaresRequirements::class);
$tool->allows('requirements')->with(['divi' => []])->andReturn(['page-write', 'other-integration', 'catalog']);
self::assertSame(['other-integration'], FeatureSets::unknownIds($tool, ['divi' => []]));
}
public function testUnknownIdsIsEmptyForDiviToolsAndAnyOtherTool(): void
{
$divi = Mockery::mock(Ability::class, RequiresDiviFeatures::class);
self::assertSame([], FeatureSets::unknownIds($divi, []));
self::assertSame([], FeatureSets::unknownIds(Mockery::mock(Ability::class), []));
}
public function testDeclaredByIsEmptyForAnyOtherTool(): void
{
self::assertSame([], FeatureSets::declaredBy(Mockery::mock(Ability::class), ['x' => 1]));