summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2018-07-30 21:00:53 +0200
committerGitHub <noreply@github.com>2018-07-30 21:00:53 +0200
commitf208ce920d7f27324f67aad9b052dfebf1d7df4e (patch)
tree140b9a7d31ffd2c404aa5177f2c8dfc875326183
parent168d6f7c44ceabe4cbf3e01af638d7310fc43ccc (diff)
parentef5074adaaac675a2fe5e250549d8092f628299c (diff)
downloadnextcloud-server-f208ce920d7f27324f67aad9b052dfebf1d7df4e.tar.gz
nextcloud-server-f208ce920d7f27324f67aad9b052dfebf1d7df4e.zip
Merge pull request #10221 from nextcloud/bugfix/noid/prevent-too-long-identifiers
Prevent too long identifier names
-rw-r--r--lib/private/DB/MigrationService.php72
-rw-r--r--tests/lib/DB/MigrationsTest.php351
2 files changed, 421 insertions, 2 deletions
diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php
index 6f5a74103a5..412bb61a086 100644
--- a/lib/private/DB/MigrationService.php
+++ b/lib/private/DB/MigrationService.php
@@ -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();
diff --git a/tests/lib/DB/MigrationsTest.php b/tests/lib/DB/MigrationsTest.php
index 191164c7eed..dd06b9096cc 100644
--- a/tests/lib/DB/MigrationsTest.php
+++ b/tests/lib/DB/MigrationsTest.php
@@ -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]);
+ }
}