From 16d3619445541a547eb3d654ef144a4ffa937540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sun, 3 May 2020 17:10:51 +0200 Subject: [PATCH] Add tests for PermissionControlledMySQLDatabaseManager --- src/Contracts/ManagesDatabaseUsers.php | 4 +- ...nantDatabaseUserAlreadyExistsException.php | 25 +++++ ...rmissionControlledMySQLDatabaseManager.php | 14 ++- tests/DatabaseUsersTest.php | 97 ++++++++++++++++++- 4 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/Exceptions/TenantDatabaseUserAlreadyExistsException.php diff --git a/src/Contracts/ManagesDatabaseUsers.php b/src/Contracts/ManagesDatabaseUsers.php index 5c45391e..6de28a80 100644 --- a/src/Contracts/ManagesDatabaseUsers.php +++ b/src/Contracts/ManagesDatabaseUsers.php @@ -6,9 +6,11 @@ namespace Stancl\Tenancy\Contracts; use Stancl\Tenancy\DatabaseConfig; -interface ManagesDatabaseUsers +interface ManagesDatabaseUsers extends TenantDatabaseManager { public function createUser(DatabaseConfig $databaseConfig): bool; public function deleteUser(DatabaseConfig $databaseConfig): bool; + + public function userExists(string $username): bool; } diff --git a/src/Exceptions/TenantDatabaseUserAlreadyExistsException.php b/src/Exceptions/TenantDatabaseUserAlreadyExistsException.php new file mode 100644 index 00000000..f84e39ec --- /dev/null +++ b/src/Exceptions/TenantDatabaseUserAlreadyExistsException.php @@ -0,0 +1,25 @@ +user} already exists."; + } + + public function __construct(string $user) + { + parent::__construct(); + + $this->user = $user; + } +} diff --git a/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php b/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php index bafcf3b8..3ce51568 100644 --- a/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php +++ b/src/TenantDatabaseManagers/PermissionControlledMySQLDatabaseManager.php @@ -6,6 +6,7 @@ namespace Stancl\Tenancy\TenantDatabaseManagers; use Stancl\Tenancy\Contracts\ManagesDatabaseUsers; use Stancl\Tenancy\DatabaseConfig; +use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException; use Stancl\Tenancy\Traits\CreatesDatabaseUsers; class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager implements ManagesDatabaseUsers @@ -25,14 +26,18 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl $hostname = $databaseConfig->connection()['host']; $password = $databaseConfig->getPassword(); + if ($this->userExists($username)) { + throw new TenantDatabaseUserAlreadyExistsException($username); + } + $this->database()->statement("CREATE USER `{$username}`@`{$hostname}` IDENTIFIED BY '{$password}'"); $grants = implode(', ', static::$grants); if ($this->isVersion8()) { // MySQL 8+ - $grantQuery = "GRANT $grants ON $database.* TO `$username`@`$hostname`"; + $grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`$hostname`"; } else { // MySQL 5.7 - $grantQuery = "GRANT $grants ON $database.* TO `$username`@`$hostname` IDENTIFIED BY '$password'"; + $grantQuery = "GRANT $grants ON `$database`.* TO `$username`@`$hostname` IDENTIFIED BY '$password'"; } return $this->database()->statement($grantQuery); @@ -49,4 +54,9 @@ class PermissionControlledMySQLDatabaseManager extends MySQLDatabaseManager impl { return $this->database()->statement("DROP USER IF EXISTS '{$databaseConfig->getUsername()}'"); } + + public function userExists(string $username): bool + { + return (bool) $this->database()->select("SELECT count(*) FROM mysql.user WHERE user = '$username'")[0]->{'count(*)'}; + } } diff --git a/tests/DatabaseUsersTest.php b/tests/DatabaseUsersTest.php index 5be409d2..b9c7021a 100644 --- a/tests/DatabaseUsersTest.php +++ b/tests/DatabaseUsersTest.php @@ -4,20 +4,111 @@ declare(strict_types=1); namespace Stancl\Tenancy\Tests; +use Illuminate\Support\Facades\DB; +use Stancl\Tenancy\Contracts\ManagesDatabaseUsers; +use Stancl\Tenancy\Tenant; +use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager; +use Illuminate\Support\Str; +use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException; +use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException; +use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager; + class DatabaseUsersTest extends TestCase { - /** @test */ - public function users_are_created_when_permission_controlled_mysql_manager_is_used() + public function setUp(): void { + parent::setUp(); + + config([ + 'tenancy.database_managers.mysql' => PermissionControlledMySQLDatabaseManager::class, + 'tenancy.database.suffix' => '', + 'tenancy.database.template_connection' => 'mysql', + ]); } /** @test */ - public function correct_grants_are_given_to_the_users() + public function users_are_created_when_permission_controlled_mysql_manager_is_used() { + $tenant = Tenant::new()->withData([ + 'id' => 'foo' . Str::random(10), + ]); + $tenant->database()->makeCredentials(); + + /** @var ManagesDatabaseUsers $manager */ + $manager = $tenant->database()->manager(); + $this->assertFalse($manager->userExists($tenant->database()->getUsername())); + + $tenant->save(); + + $this->assertTrue($manager->userExists($tenant->database()->getUsername())); + } + + /** @test */ + public function a_tenants_database_cannot_be_created_when_the_user_already_exists() + { + $username = 'foo' . Str::random(8); + $tenant = Tenant::new()->withData([ + '_tenancy_db_username' => $username, + ])->save(); + + /** @var ManagesDatabaseUsers $manager */ + $manager = $tenant->database()->manager(); + $this->assertTrue($manager->userExists($tenant->database()->getUsername())); + $this->assertTrue($manager->databaseExists($tenant->database()->getName())); + + $this->expectException(TenantDatabaseUserAlreadyExistsException::class); + $tenant2 = Tenant::new()->withData([ + '_tenancy_db_username' => $username, + ])->save(); + + /** @var ManagesDatabaseUsers $manager */ + $manager = $tenant2->database()->manager(); + // database was not created because of DB transaction + $this->assertFalse($manager->databaseExists($tenant2->database()->getName())); + } + + /** @test */ + public function correct_grants_are_given_to_users() + { + PermissionControlledMySQLDatabaseManager::$grants = [ + 'ALTER', 'ALTER ROUTINE', 'CREATE', + ]; + + $tenant = Tenant::new()->withData([ + '_tenancy_db_username' => $user = 'user' . Str::random(8), + ])->save(); + + $query = DB::connection('mysql')->select("SHOW GRANTS FOR `{$tenant->database()->getUsername()}`@`{$tenant->database()->connection()['host']}`")[1]; + $this->assertStringStartsWith("GRANT CREATE, ALTER, ALTER ROUTINE ON", $query->{"Grants for {$user}@mysql"}); // @mysql because that's the hostname within the docker network } /** @test */ public function having_existing_databases_without_users_and_switching_to_permission_controlled_mysql_manager_doesnt_break_existing_dbs() { + config([ + 'tenancy.database_managers.mysql' => MySQLDatabaseManager::class, + 'tenancy.database.suffix' => '', + 'tenancy.database.template_connection' => 'mysql', + ]); + + $tenant = Tenant::new()->withData([ + 'id' => 'foo' . Str::random(10), + ])->save(); + + $this->assertTrue($tenant->database()->manager() instanceof MySQLDatabaseManager); + + $tenant = Tenant::new()->withData([ + 'id' => 'foo' . Str::random(10), + ])->save(); + + tenancy()->initialize($tenant); // check if everything works + tenancy()->end(); + + config(['tenancy.database_managers.mysql' => PermissionControlledMySQLDatabaseManager::class]); + + tenancy()->initialize($tenant); // check if everything works + + $this->assertTrue($tenant->database()->manager() instanceof PermissionControlledMySQLDatabaseManager); + $this->assertSame('root', config('database.connections.tenant.username')); } }