Skip to content

Commit a6f66aa

Browse files
authored
Fix release readiness issues for 5.x (#1001)
* Fix release readiness issues for 5.x - Add cycle detection to seed dependency ordering to prevent infinite recursion when seeds have circular dependencies - Add 'unsigned' to valid column options for API consistency with 'signed' - Fix SQL injection vulnerability in MysqlAdapter by replacing addslashes() with proper driver escaping for column comments - Fix non-existent $io->error() method call in DumpCommand (should be err()) - Document SQL Server check constraints as unsupported with improved error messages guiding users to use raw SQL * Fix additional release readiness issues from deep dive - Restrict unserialize() to safe CakePHP schema classes only - Fix strpos() logic bug by using str_contains() - Initialize $command property to prevent uninitialized access - Fix weak equality (!=) to strict (!==) in Table::saveData() - Fix copy-paste bug in Migrator (was using 'down' instead of 'missing') - Replace assert() with explicit RuntimeException in BaseSeed * Add missing CakePHP schema classes to unserialize allowlist TableSchema contains nested Column, Index, Constraint and other schema objects that also need to be allowed for deserialization. * Fix docblock annotation spacing in SqlserverAdapter
1 parent bf08df2 commit a6f66aa

File tree

10 files changed

+82
-15
lines changed

10 files changed

+82
-15
lines changed

src/BaseSeed.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,9 @@ public function isIdempotent(): bool
238238
public function call(string $seeder, array $options = []): void
239239
{
240240
$io = $this->getIo();
241-
assert($io !== null, 'Requires ConsoleIo');
241+
if ($io === null) {
242+
throw new RuntimeException('ConsoleIo is required for calling other seeders.');
243+
}
242244
$io->out('');
243245
$io->out(
244246
' ====' .
@@ -285,7 +287,9 @@ protected function runCall(string $seeder, array $options = []): void
285287
'source' => $options['source'],
286288
]);
287289
$io = $this->getIo();
288-
assert($io !== null, 'Missing ConsoleIo instance');
290+
if ($io === null) {
291+
throw new RuntimeException('ConsoleIo is required for calling other seeders.');
292+
}
289293
$manager = $factory->createManager($io);
290294
$manager->seed($seeder);
291295
}

src/Command/BakeMigrationDiffCommand.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,15 @@
1919
use Cake\Console\ConsoleIo;
2020
use Cake\Console\ConsoleOptionParser;
2121
use Cake\Database\Connection;
22+
use Cake\Database\Schema\CachedCollection;
23+
use Cake\Database\Schema\CheckConstraint;
2224
use Cake\Database\Schema\CollectionInterface;
25+
use Cake\Database\Schema\Column;
26+
use Cake\Database\Schema\Constraint;
27+
use Cake\Database\Schema\ForeignKey;
28+
use Cake\Database\Schema\Index;
2329
use Cake\Database\Schema\TableSchema;
30+
use Cake\Database\Schema\UniqueKey;
2431
use Cake\Datasource\ConnectionManager;
2532
use Cake\Event\Event;
2633
use Cake\Event\EventManager;
@@ -485,7 +492,7 @@ protected function checkSync(): bool
485492
$lastVersion = $this->migratedItems[0]['version'];
486493
$lastFile = end($this->migrationsFiles);
487494

488-
return $lastFile && (bool)strpos($lastFile, (string)$lastVersion);
495+
return $lastFile && str_contains($lastFile, (string)$lastVersion);
489496
}
490497

491498
return false;
@@ -546,7 +553,21 @@ protected function getDumpSchema(Arguments $args): array
546553
$this->io->abort($msg);
547554
}
548555

549-
return unserialize((string)file_get_contents($path));
556+
$contents = (string)file_get_contents($path);
557+
558+
// Use allowed_classes to restrict deserialization to safe CakePHP schema classes
559+
return unserialize($contents, [
560+
'allowed_classes' => [
561+
TableSchema::class,
562+
CachedCollection::class,
563+
Column::class,
564+
Index::class,
565+
Constraint::class,
566+
UniqueKey::class,
567+
ForeignKey::class,
568+
CheckConstraint::class,
569+
],
570+
]);
550571
}
551572

552573
/**

src/Command/DumpCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
141141

142142
return self::CODE_SUCCESS;
143143
}
144-
$io->error("An error occurred while writing dump file `{$filePath}`");
144+
$io->err("<error>An error occurred while writing dump file `{$filePath}`</error>");
145145

146146
return self::CODE_ERROR;
147147
}

src/Db/Adapter/MysqlAdapter.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,9 @@ protected function getRenameColumnInstructions(string $tableName, string $column
725725
foreach ($rows as $row) {
726726
if (strcasecmp($row['Field'], $columnName) === 0) {
727727
$null = $row['Null'] === 'NO' ? 'NOT NULL' : 'NULL';
728-
$comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : '';
728+
$comment = isset($row['Comment']) && $row['Comment'] !== ''
729+
? ' COMMENT ' . $this->getConnection()->getDriver()->schemaValue($row['Comment'])
730+
: '';
729731

730732
// create the extra string by also filtering out the DEFAULT_GENERATED option (MySQL 8 fix)
731733
$extras = array_filter(

src/Db/Adapter/SqlserverAdapter.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,27 +1114,38 @@ private function updateSQLForIdentityInsert(string $tableName, string $sql): str
11141114

11151115
/**
11161116
* @inheritDoc
1117+
*
1118+
* Note: Check constraints are not supported for SQL Server adapter.
1119+
* This method returns an empty array. Use raw SQL via execute() if you need
1120+
* check constraints on SQL Server.
11171121
*/
11181122
protected function getCheckConstraints(string $tableName): array
11191123
{
1120-
// TODO: Implement check constraints for SQL Server
11211124
return [];
11221125
}
11231126

11241127
/**
11251128
* @inheritDoc
1129+
* @throws \BadMethodCallException Check constraints are not supported for SQL Server.
11261130
*/
11271131
protected function getAddCheckConstraintInstructions(TableMetadata $table, CheckConstraint $checkConstraint): AlterInstructions
11281132
{
1129-
throw new BadMethodCallException('Check constraints are not yet implemented for SQL Server adapter');
1133+
throw new BadMethodCallException(
1134+
'Check constraints are not supported for the SQL Server adapter. ' .
1135+
'Use $this->execute() with raw SQL to add check constraints.',
1136+
);
11301137
}
11311138

11321139
/**
11331140
* @inheritDoc
1141+
* @throws \BadMethodCallException Check constraints are not supported for SQL Server.
11341142
*/
11351143
protected function getDropCheckConstraintInstructions(string $tableName, string $constraintName): AlterInstructions
11361144
{
1137-
throw new BadMethodCallException('Check constraints are not yet implemented for SQL Server adapter');
1145+
throw new BadMethodCallException(
1146+
'Check constraints are not supported for the SQL Server adapter. ' .
1147+
'Use $this->execute() with raw SQL to drop check constraints.',
1148+
);
11381149
}
11391150

11401151
/**

src/Db/Table.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ public function saveData(): void
955955
$c = array_keys($row);
956956
foreach ($this->getData() as $row) {
957957
$k = array_keys($row);
958-
if ($k != $c) {
958+
if ($k !== $c) {
959959
$bulk = false;
960960
break;
961961
}

src/Db/Table/Column.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ protected function getValidOptions(): array
789789
'update',
790790
'comment',
791791
'signed',
792+
'unsigned',
792793
'timezone',
793794
'properties',
794795
'values',

src/Migration/Manager.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,18 +1101,46 @@ protected function getSeedDependenciesInstances(SeedInterface $seed): array
11011101
* Order seeds by dependencies
11021102
*
11031103
* @param \Migrations\SeedInterface[] $seeds Seeds
1104+
* @param array<string, true> $visiting Seeds currently being visited (for cycle detection)
1105+
* @param array<string, true> $visited Seeds that have been fully processed
11041106
* @return \Migrations\SeedInterface[]
1107+
* @throws \RuntimeException When a circular dependency is detected
11051108
*/
1106-
protected function orderSeedsByDependencies(array $seeds): array
1109+
protected function orderSeedsByDependencies(array $seeds, array $visiting = [], array &$visited = []): array
11071110
{
11081111
$orderedSeeds = [];
11091112
foreach ($seeds as $seed) {
11101113
$name = $seed->getName();
1111-
$orderedSeeds[$name] = $seed;
1114+
1115+
// Skip if already fully processed
1116+
if (isset($visited[$name])) {
1117+
continue;
1118+
}
1119+
1120+
// Check for circular dependency
1121+
if (isset($visiting[$name])) {
1122+
$cycle = array_keys($visiting);
1123+
$cycle[] = $name;
1124+
throw new RuntimeException(
1125+
'Circular dependency detected in seeds: ' . implode(' -> ', $cycle),
1126+
);
1127+
}
1128+
1129+
// Mark as currently visiting
1130+
$visiting[$name] = true;
1131+
11121132
$dependencies = $this->getSeedDependenciesInstances($seed);
11131133
if ($dependencies) {
1114-
$orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
1134+
$orderedSeeds = array_merge(
1135+
$this->orderSeedsByDependencies($dependencies, $visiting, $visited),
1136+
$orderedSeeds,
1137+
);
11151138
}
1139+
1140+
// Mark as fully visited and add to result
1141+
$visited[$name] = true;
1142+
unset($visiting[$name]);
1143+
$orderedSeeds[$name] = $seed;
11161144
}
11171145

11181146
return $orderedSeeds;

src/Migrations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Migrations
3636
*
3737
* @var string
3838
*/
39-
protected string $command;
39+
protected string $command = '';
4040

4141
/**
4242
* Constructor

src/TestSuite/Migrator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ protected function shouldDropTables(Migrations $migrations, array $options): boo
199199
if (!empty($messages['missing'])) {
200200
$hasProblems = true;
201201
$output[] = 'Applied but missing migrations:';
202-
$output = array_merge($output, array_map($itemize, $messages['down']));
202+
$output = array_merge($output, array_map($itemize, $messages['missing']));
203203
$output[] = '';
204204
}
205205
if ($output) {

0 commit comments

Comments
 (0)