From 702d0388d7899b91c4626c86887952585a499eae Mon Sep 17 00:00:00 2001 From: Aleksey Tupichenkov Date: Mon, 15 Sep 2025 19:26:51 +0300 Subject: [PATCH] feat: added `disallowedSchemaNames` argument for security reasons --- README.md | 11 ++++++++++- .../Doctrine/DoctrineSchemaDropCommand.php | 17 +++++++++++++++-- .../DoctrineSchemaFixturesLoadCommand.php | 12 ++++++++++++ .../Doctrine/DoctrineSchemaDropCommandTest.php | 16 +++++++++++++++- .../DoctrineSchemaFixturesLoadCommandTest.php | 16 +++++++++++++++- 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8656e70..4f407e1 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,10 @@ Drops a PostgreSQL schema and all its objects: ```yaml # config/services.yaml services: - SharedServices\Command\Doctrine\DoctrineSchemaDropCommand: ~ + SharedServices\Command\Doctrine\DoctrineSchemaDropCommand: + arguments: + - '@Doctrine\DBAL\Connection' + - ['public'] # Disallowed schema names for safety ``` Usage: @@ -104,6 +107,8 @@ Usage: php bin/console doctrine:schema:delete ``` +**Security Note:** You can specify disallowed schema names to prevent accidental deletion of critical schemas like `public`. + ### Schema Migrations Command Runs Doctrine migrations within a specific schema. Creates the schema if it doesn't exist: @@ -129,6 +134,8 @@ services: SharedServices\Command\Doctrine\DoctrineSchemaFixturesLoadCommand: arguments: - '@doctrine.fixtures_load_command' + - '@Doctrine\DBAL\Connection' + - ['public'] # Disallowed schema names for safety ``` Usage: @@ -136,6 +143,8 @@ Usage: php bin/console doctrine:schema:fixtures:load [options] ``` +**Security Note:** You can specify disallowed schema names to prevent accidental fixture loading into critical schemas like `public`. + **Note:** These commands are optional and should only be registered if you're using the corresponding Doctrine features (migrations and/or fixtures) in your project. ## Testing diff --git a/src/Command/Doctrine/DoctrineSchemaDropCommand.php b/src/Command/Doctrine/DoctrineSchemaDropCommand.php index 7fe8ab4..cd352b3 100644 --- a/src/Command/Doctrine/DoctrineSchemaDropCommand.php +++ b/src/Command/Doctrine/DoctrineSchemaDropCommand.php @@ -11,8 +11,13 @@ class DoctrineSchemaDropCommand extends AbstractDoctrineSchemaCommand { - public function __construct(Connection $connection) - { + /** + * @param string[] $disallowedSchemaNames + */ + public function __construct( + Connection $connection, + private readonly array $disallowedSchemaNames = [], + ) { parent::__construct('doctrine:schema:delete', $connection); } @@ -22,6 +27,14 @@ protected function execute( ): int { $schema = $this->getSchemaFromInput($input); + if (in_array($schema, $this->disallowedSchemaNames, true)) { + $output->writeln( + "Command is disallowed from being called for the '$schema' schema" + ); + + return Command::FAILURE; + } + $output->writeln("Drop schema '{$schema}'..."); $quotedSchema = $this->connection->quoteIdentifier($schema); diff --git a/src/Command/Doctrine/DoctrineSchemaFixturesLoadCommand.php b/src/Command/Doctrine/DoctrineSchemaFixturesLoadCommand.php index b450f59..d3400b8 100644 --- a/src/Command/Doctrine/DoctrineSchemaFixturesLoadCommand.php +++ b/src/Command/Doctrine/DoctrineSchemaFixturesLoadCommand.php @@ -13,9 +13,13 @@ class DoctrineSchemaFixturesLoadCommand extends AbstractNestingDoctrineSchemaCommand { + /** + * @param string[] $disallowedSchemaNames + */ public function __construct( LoadDataFixturesDoctrineCommand $parentCommand, Connection $connection, + private readonly array $disallowedSchemaNames = [], ) { parent::__construct('doctrine:schema:fixtures:load', $parentCommand, $connection); } @@ -27,6 +31,14 @@ protected function execute( try { $schema = $this->getSchemaFromInput($input); + if (in_array($schema, $this->disallowedSchemaNames, true)) { + $output->writeln( + "Command is disallowed from being called for the '$schema' schema" + ); + + return Command::FAILURE; + } + if (!$this->isSchemaExist($schema)) { $output->writeln("Schema '{$schema}' doesn't exist"); diff --git a/tests/Command/Doctrine/DoctrineSchemaDropCommandTest.php b/tests/Command/Doctrine/DoctrineSchemaDropCommandTest.php index 4d0e409..7a1dbf1 100644 --- a/tests/Command/Doctrine/DoctrineSchemaDropCommandTest.php +++ b/tests/Command/Doctrine/DoctrineSchemaDropCommandTest.php @@ -20,7 +20,7 @@ class DoctrineSchemaDropCommandTest extends TestCase protected function setUp(): void { $this->connection = $this->createMock(Connection::class); - $this->command = new DoctrineSchemaDropCommand($this->connection); + $this->command = new DoctrineSchemaDropCommand($this->connection, ['public']); } public function testSuccess(): void @@ -41,4 +41,18 @@ public function testSuccess(): void $this->assertEquals(Command::SUCCESS, $result); } + + public function testDisallowedSchemaNameFail(): void + { + $input = new ArrayInput(['schema' => 'public']); + $output = new BufferedOutput(); + + $result = $this->command->run($input, $output); + + $this->assertStringContainsString( + "Command is disallowed from being called for the 'public' schema", + $output->fetch() + ); + $this->assertEquals(Command::FAILURE, $result); + } } diff --git a/tests/Command/Doctrine/DoctrineSchemaFixturesLoadCommandTest.php b/tests/Command/Doctrine/DoctrineSchemaFixturesLoadCommandTest.php index 696ec42..7125640 100644 --- a/tests/Command/Doctrine/DoctrineSchemaFixturesLoadCommandTest.php +++ b/tests/Command/Doctrine/DoctrineSchemaFixturesLoadCommandTest.php @@ -34,7 +34,7 @@ protected function setUp(): void new InputOption('no-interaction'), ])); - $this->command = new DoctrineSchemaFixturesLoadCommand($this->parentCommand, $this->connection); + $this->command = new DoctrineSchemaFixturesLoadCommand($this->parentCommand, $this->connection, ['public']); $this->command->setApplication($this->application); } @@ -72,4 +72,18 @@ public function testSuccess(): void $this->assertEquals(Command::SUCCESS, $result); $this->assertStringContainsString("Load fixtures for 'test_schema'...", $output->fetch()); } + + public function testDisallowedSchemaNameFail(): void + { + $input = new ArrayInput(['schema' => 'public']); + $output = new BufferedOutput(); + + $result = $this->command->run($input, $output); + + $this->assertStringContainsString( + "Command is disallowed from being called for the 'public' schema", + $output->fetch() + ); + $this->assertEquals(Command::FAILURE, $result); + } }