Merge pull request #10221 from nextcloud/bugfix/noid/prevent-too-long-identifiers

Prevent too long identifier names
This commit is contained in:
Roeland Jago Douma 2018-07-30 21:00:53 +02:00 committed by GitHub
commit f208ce920d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 421 additions and 2 deletions

View file

@ -23,7 +23,13 @@
namespace OC\DB;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Sequence;
use OC\IntegrityCheck\Helpers\AppLocator;
use OC\Migration\SimpleOutput;
use OCP\AppFramework\App;
@ -450,7 +456,9 @@ class MigrationService {
}, ['tablePrefix' => $this->connection->getPrefix()]);
if ($toSchema instanceof SchemaWrapper) {
$this->connection->migrateToSchema($toSchema->getWrappedSchema());
$targetSchema = $toSchema->getWrappedSchema();
$this->ensureOracleIdentifierLengthLimit($targetSchema, strlen($this->connection->getPrefix()));
$this->connection->migrateToSchema($targetSchema);
$toSchema->performDropTableCalls();
}
@ -463,6 +471,68 @@ class MigrationService {
$this->markAsExecuted($version);
}
public function ensureOracleIdentifierLengthLimit(Schema $schema, int $prefixLength) {
$sequences = $schema->getSequences();
foreach ($schema->getTables() as $table) {
if (\strlen($table->getName()) - $prefixLength > 27) {
throw new \InvalidArgumentException('Table name "' . $table->getName() . '" is too long.');
}
foreach ($table->getColumns() as $thing) {
if (\strlen($thing->getName()) - $prefixLength > 27) {
throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
}
}
foreach ($table->getIndexes() as $thing) {
if (\strlen($thing->getName()) - $prefixLength > 27) {
throw new \InvalidArgumentException('Index name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
}
}
foreach ($table->getForeignKeys() as $thing) {
if (\strlen($thing->getName()) - $prefixLength > 27) {
throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
}
}
$primaryKey = $table->getPrimaryKey();
if ($primaryKey instanceof Index) {
$indexName = strtolower($primaryKey->getName());
$isUsingDefaultName = $indexName === 'primary';
if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
$defaultName = $table->getName() . '_pkey';
$isUsingDefaultName = strtolower($defaultName) === $indexName;
if ($isUsingDefaultName) {
$sequenceName = $table->getName() . '_' . implode('_', $primaryKey->getColumns()) . '_seq';
$sequences = array_filter($sequences, function(Sequence $sequence) use ($sequenceName) {
return $sequence->getName() !== $sequenceName;
});
}
} else if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
$defaultName = $table->getName() . '_seq';
$isUsingDefaultName = strtolower($defaultName) === $indexName;
}
if (!$isUsingDefaultName && \strlen($indexName) - $prefixLength > 27) {
throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
}
if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength > 23) {
throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
}
}
}
foreach ($sequences as $sequence) {
if (\strlen($sequence->getName()) - $prefixLength > 27) {
throw new \InvalidArgumentException('Sequence name "' . $sequence->getName() . '" is too long.');
}
}
}
private function ensureMigrationsAreLoaded() {
if (empty($this->migrations)) {
$this->migrations = $this->findMigrations();

View file

@ -10,7 +10,14 @@
namespace Test\DB;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use OC\DB\Connection;
use OC\DB\MigrationService;
use OC\DB\SchemaWrapper;
@ -94,10 +101,18 @@ class MigrationsTest extends \Test\TestCase {
$this->db->expects($this->once())
->method('migrateToSchema');
$wrappedSchema = $this->createMock(Schema::class);
$wrappedSchema->expects($this->once())
->method('getTables')
->willReturn([]);
$wrappedSchema->expects($this->once())
->method('getSequences')
->willReturn([]);
$schemaResult = $this->createMock(SchemaWrapper::class);
$schemaResult->expects($this->once())
->method('getWrappedSchema')
->willReturn($this->createMock(Schema::class));
->willReturn($wrappedSchema);
$step = $this->createMock(IMigrationStep::class);
$step->expects($this->at(0))
@ -205,4 +220,338 @@ class MigrationsTest extends \Test\TestCase {
->withConsecutive(['20170130180002'], ['20170130180003']);
$this->migrationService->migrate();
}
public function testEnsureOracleIdentifierLengthLimitValid() {
$column = $this->createMock(Column::class);
$column->expects($this->once())
->method('getName')
->willReturn(\str_repeat('a', 30));
$index = $this->createMock(Index::class);
$index->expects($this->once())
->method('getName')
->willReturn(\str_repeat('a', 30));
$foreignKey = $this->createMock(ForeignKeyConstraint::class);
$foreignKey->expects($this->once())
->method('getName')
->willReturn(\str_repeat('a', 30));
$table = $this->createMock(Table::class);
$table->expects($this->once())
->method('getName')
->willReturn(\str_repeat('a', 30));
$sequence = $this->createMock(Sequence::class);
$sequence->expects($this->once())
->method('getName')
->willReturn(\str_repeat('a', 30));
$table->expects($this->once())
->method('getColumns')
->willReturn([$column]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([$index]);
$table->expects($this->once())
->method('getForeignKeys')
->willReturn([$foreignKey]);
$table->expects($this->once())
->method('getPrimaryKey')
->willReturn(null);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
$schema->expects($this->once())
->method('getSequences')
->willReturn([$sequence]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKey() {
$index = $this->createMock(Index::class);
$index->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 30));
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 26));
$table->expects($this->once())
->method('getColumns')
->willReturn([]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([]);
$table->expects($this->once())
->method('getForeignKeys')
->willReturn([]);
$table->expects($this->once())
->method('getPrimaryKey')
->willReturn($index);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
$schema->expects($this->once())
->method('getSequences')
->willReturn([]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKeyDefault() {
$defaultName = 'PRIMARY';
if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) {
$defaultName = \str_repeat('a', 26) . '_' . \str_repeat('b', 30) . '_seq';
} else if ($this->db->getDatabasePlatform() instanceof OraclePlatform) {
$defaultName = \str_repeat('a', 26) . '_seq';
}
$index = $this->createMock(Index::class);
$index->expects($this->any())
->method('getName')
->willReturn($defaultName);
$index->expects($this->any())
->method('getColumns')
->willReturn([\str_repeat('b', 30)]);
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 26));
$table->expects($this->once())
->method('getColumns')
->willReturn([]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([]);
$table->expects($this->once())
->method('getForeignKeys')
->willReturn([]);
$table->expects($this->once())
->method('getPrimaryKey')
->willReturn($index);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
$schema->expects($this->once())
->method('getSequences')
->willReturn([]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongTableName() {
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 31));
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithDefault() {
$defaultName = 'PRIMARY';
if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) {
$defaultName = \str_repeat('a', 27) . '_' . \str_repeat('b', 30) . '_seq';
} else if ($this->db->getDatabasePlatform() instanceof OraclePlatform) {
$defaultName = \str_repeat('a', 27) . '_seq';
}
$index = $this->createMock(Index::class);
$index->expects($this->any())
->method('getName')
->willReturn($defaultName);
$index->expects($this->any())
->method('getColumns')
->willReturn([\str_repeat('b', 30)]);
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 27));
$table->expects($this->once())
->method('getColumns')
->willReturn([]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([]);
$table->expects($this->once())
->method('getForeignKeys')
->willReturn([]);
$table->expects($this->once())
->method('getPrimaryKey')
->willReturn($index);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithName() {
$index = $this->createMock(Index::class);
$index->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 31));
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 26));
$table->expects($this->once())
->method('getColumns')
->willReturn([]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([]);
$table->expects($this->once())
->method('getForeignKeys')
->willReturn([]);
$table->expects($this->once())
->method('getPrimaryKey')
->willReturn($index);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongColumnName() {
$column = $this->createMock(Column::class);
$column->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 31));
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 30));
$table->expects($this->once())
->method('getColumns')
->willReturn([$column]);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongIndexName() {
$index = $this->createMock(Index::class);
$index->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 31));
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 30));
$table->expects($this->once())
->method('getColumns')
->willReturn([]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([$index]);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongForeignKeyName() {
$foreignKey = $this->createMock(ForeignKeyConstraint::class);
$foreignKey->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 31));
$table = $this->createMock(Table::class);
$table->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 30));
$table->expects($this->once())
->method('getColumns')
->willReturn([]);
$table->expects($this->once())
->method('getIndexes')
->willReturn([]);
$table->expects($this->once())
->method('getForeignKeys')
->willReturn([$foreignKey]);
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([$table]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsureOracleIdentifierLengthLimitTooLongSequenceName() {
$sequence = $this->createMock(Sequence::class);
$sequence->expects($this->any())
->method('getName')
->willReturn(\str_repeat('a', 31));
$schema = $this->createMock(Schema::class);
$schema->expects($this->once())
->method('getTables')
->willReturn([]);
$schema->expects($this->once())
->method('getSequences')
->willReturn([$sequence]);
self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
}
}