From ef46bfb8847c9f4f1d199aa4ccd6547e830dae8f Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Thu, 10 Dec 2020 13:07:22 -0500 Subject: [PATCH] Account for special '$user' variable appearing as first schema in search_path Given that the default search_path value in a PostgreSQL installation is '"$user", public', or perhaps just '"$user"', as may be the case if hardened against CVE-2018-1058, it is preferable to account for the possibility that an end-user may wish to configure a PostgreSQL database connection in Laravel to mimic said default. Now, if '$user' is the first schema in the search_path, the PostgresBuilder will resolve that schema name to the username defined on the database connection whenever appropriate, e.g., in the hasTable() and getColumnListing() methods. --- .../Database/Schema/PostgresBuilder.php | 4 +- .../Database/DatabasePostgresBuilderTest.php | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 37d01fce4590..0cba5f4d9a16 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -181,7 +181,9 @@ protected function parseSchemaAndTable($reference) // We will use the default schema unless the schema has been specified in the // query. If the schema has been specified in the query then we can use it // instead of a default schema configured in the connection search path. - $schema = $searchPath[0]; + $schema = $searchPath[0] === '$user' + ? $this->connection->getConfig('username') + : $searchPath[0]; if (count($parts) === 2) { $schema = $parts[0]; diff --git a/tests/Database/DatabasePostgresBuilderTest.php b/tests/Database/DatabasePostgresBuilderTest.php index e20e24d1213f..f41e271a4428 100644 --- a/tests/Database/DatabasePostgresBuilderTest.php +++ b/tests/Database/DatabasePostgresBuilderTest.php @@ -58,6 +58,29 @@ public function testWhenSearchPathNotEmptyHasTableWithUnqualifiedSchemaReference $builder->hasTable('foo'); } + /** + * Ensure that when the reference is unqualified (i.e., does not contain a + * database name or a schema), and the first schema in the search_path is + * the special variable '$user', the database specified on the connection is + * used, the first schema in the search_path is used, and the variable + * resolves to the username specified on the connection. + */ + public function testWhenFirstSchemaInSearchPathIsVariableHasTableWithUnqualifiedSchemaReferenceIsCorrect() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('username')->andReturn('foouser'); + $connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user'); + $grammar = m::mock(PostgresGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); + $connection->shouldReceive('select')->with("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'", ['laravel', 'foouser', 'foo'])->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $builder = $this->getBuilder($connection); + + $builder->hasTable('foo'); + } + /** * Ensure that when the reference is qualified only with a schema, that * the database specified on the connection is used, and the specified @@ -146,6 +169,32 @@ public function testWhenSearchPathNotEmptyGetColumnListingWithUnqualifiedSchemaR $builder->getColumnListing('foo'); } + /** + * Ensure that when the reference is unqualified (i.e., does not contain a + * database name or a schema), and the first schema in the search_path is + * the special variable '$user', the database specified on the connection is + * used, the first schema in the search_path is used, and the variable + * resolves to the username specified on the connection. + */ + public function testWhenFirstSchemaInSearchPathIsVariableGetColumnListingWithUnqualifiedSchemaReferenceIsCorrect() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('username')->andReturn('foouser'); + $connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user'); + $grammar = m::mock(PostgresGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileColumnListing')->andReturn('select column_name from information_schema.columns where table_catalog = ? and table_schema = ? and table_name = ?'); + $connection->shouldReceive('select')->with('select column_name from information_schema.columns where table_catalog = ? and table_schema = ? and table_name = ?', ['laravel', 'foouser', 'foo'])->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumnListing')->andReturn(['some_column']); + $builder = $this->getBuilder($connection); + + $builder->getColumnListing('foo'); + } + /** * Ensure that when the reference is qualified only with a schema, that * the database specified on the connection is used, and the specified