diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index cebbebe74..9dc3b8a13 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -312,12 +312,12 @@ class ServersController extends Controller * Creates a new database assigned to a specific server. * * @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request - * @param int $server + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * - * @throws \Exception + * @throws \Throwable */ - public function newDatabase(StoreServerDatabaseRequest $request, $server) + public function newDatabase(StoreServerDatabaseRequest $request, Server $server) { $this->databaseManagementService->create($server, [ 'database' => $request->input('database'), @@ -326,7 +326,7 @@ class ServersController extends Controller 'max_connections' => $request->input('max_connections'), ]); - return redirect()->route('admin.servers.view.database', $server)->withInput(); + return redirect()->route('admin.servers.view.database', $server->id)->withInput(); } /** diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index f556aff57..24c8906aa 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -57,13 +57,12 @@ class DatabaseController extends ApplicationApiController * server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request + * @param \Pterodactyl\Models\Server $server * @return array */ - public function index(GetServerDatabasesRequest $request): array + public function index(GetServerDatabasesRequest $request, Server $server): array { - $databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id); - - return $this->fractal->collection($databases) + return $this->fractal->collection($server->databases) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) ->toArray(); } @@ -72,11 +71,13 @@ class DatabaseController extends ApplicationApiController * Return a single server database. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database * @return array */ - public function view(GetServerDatabaseRequest $request): array + public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { - return $this->fractal->item($request->getModel(Database::class)) + return $this->fractal->item($database) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) ->toArray(); } @@ -85,29 +86,31 @@ class DatabaseController extends ApplicationApiController * Reset the password for a specific server database. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database + * @return \Illuminate\Http\JsonResponse * * @throws \Throwable */ - public function resetPassword(ServerDatabaseWriteRequest $request): Response + public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse { - $this->databasePasswordService->handle($request->getModel(Database::class)); + $this->databasePasswordService->handle($database); - return response('', 204); + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); } /** * Create a new database on the Panel for a given server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\JsonResponse * - * @throws \Exception + * @throws \Throwable */ - public function store(StoreServerDatabaseRequest $request): JsonResponse + public function store(StoreServerDatabaseRequest $request, Server $server): JsonResponse { - $server = $request->getModel(Server::class); - $database = $this->databaseManagementService->create($server->id, $request->validated()); + $database = $this->databaseManagementService->create($server, $request->validated()); return $this->fractal->item($database) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) @@ -117,7 +120,7 @@ class DatabaseController extends ApplicationApiController 'database' => $database->id, ]), ]) - ->respond(201); + ->respond(Response::HTTP_CREATED); } /** diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 0c305c2e6..5a3e1e3ac 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -69,9 +69,7 @@ class DatabaseController extends ClientApiController */ public function index(GetDatabasesRequest $request, Server $server): array { - $databases = $this->repository->getDatabasesForServer($server->id); - - return $this->fractal->collection($databases) + return $this->fractal->collection($server->databases) ->transformWith($this->getTransformer(DatabaseTransformer::class)) ->toArray(); } @@ -83,6 +81,8 @@ class DatabaseController extends ClientApiController * @param \Pterodactyl\Models\Server $server * @return array * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException */ public function store(StoreDatabaseRequest $request, Server $server): array diff --git a/app/Models/Database.php b/app/Models/Database.php index ae20a51c8..42fbb1acc 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,6 +2,21 @@ namespace Pterodactyl\Models; +/** + * @property int $id + * @property int $server_id + * @property int $database_host_id + * @property string $database + * @property string $username + * @property string $remote + * @property string $password + * @property int $max_connections + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Server $server + * @property \Pterodactyl\Models\DatabaseHost $host + */ class Database extends Model { /** diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index b98a757db..7de6e2929 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -3,19 +3,22 @@ namespace Pterodactyl\Services\Databases; use Exception; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Helpers\Utilities; -use Illuminate\Database\DatabaseManager; +use Illuminate\Database\ConnectionInterface; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException; +use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; class DatabaseManagementService { /** - * @var \Illuminate\Database\DatabaseManager + * @var \Illuminate\Database\ConnectionInterface */ - private $database; + private $connection; /** * @var \Pterodactyl\Extensions\DynamicDatabaseConnection @@ -33,84 +36,113 @@ class DatabaseManagementService private $repository; /** + * Determines if the service should validate the user's ability to create an additional + * database for this server. In almost all cases this should be true, but to keep things + * flexible you can also set it to false and create more databases than the server is + * allocated. + * * @var bool */ - protected $useRandomHost = false; + protected $validateDatabaseLimit = true; /** * CreationService constructor. * - * @param \Illuminate\Database\DatabaseManager $database + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( - DatabaseManager $database, + ConnectionInterface $connection, DynamicDatabaseConnection $dynamic, DatabaseRepositoryInterface $repository, Encrypter $encrypter ) { - $this->database = $database; + $this->connection = $connection; $this->dynamic = $dynamic; $this->encrypter = $encrypter; $this->repository = $repository; } + /** + * Set wether or not this class should validate that the server has enough slots + * left before creating the new database. + * + * @param bool $validate + * @return $this + */ + public function setValidateDatabaseLimit(bool $validate): self + { + $this->validateDatabaseLimit = $validate; + + return $this; + } + /** * Create a new database that is linked to a specific host. * - * @param int $server + * @param \Pterodactyl\Models\Server $server * @param array $data * @return \Pterodactyl\Models\Database * - * @throws \Exception + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException + * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException */ - public function create($server, array $data) + public function create(Server $server, array $data) { - $data['server_id'] = $server; - $data['database'] = sprintf('s%d_%s', $server, $data['database']); - $data['username'] = sprintf('u%d_%s', $server, str_random(10)); - $data['password'] = $this->encrypter->encrypt( - Utilities::randomStringWithSpecialCharacters(24) - ); + if (! config('pterodactyl.client_features.databases.enabled')) { + throw new DatabaseClientFeatureNotEnabledException; + } + + if ($this->validateDatabaseLimit) { + // If the server has a limit assigned and we've already reached that limit, throw back + // an exception and kill the process. + if (! is_null($server->database_limit) && $server->databases()->count() >= $server->database_limit) { + throw new TooManyDatabasesException; + } + } + + $data = array_merge($data, [ + 'server_id' => $server->id, + 'database' => sprintf('s%d_%s', $server->id, $data['database']), + 'username' => sprintf('u%d_%s', $server->id, str_random(10)), + 'password' => $this->encrypter->encrypt( + Utilities::randomStringWithSpecialCharacters(24) + ), + ]); + + $database = null; - $this->database->beginTransaction(); try { - $database = $this->repository->createIfNotExists($data); - $this->dynamic->set('dynamic', $data['database_host_id']); + return $this->connection->transaction(function () use ($data, &$database) { + $database = $this->repository->createIfNotExists($data); + $this->dynamic->set('dynamic', $data['database_host_id']); - $this->repository->createDatabase($database->database); - $this->repository->createUser( - $database->username, - $database->remote, - $this->encrypter->decrypt($database->password), - $database->max_connections - ); - $this->repository->assignUserToDatabase( - $database->database, - $database->username, - $database->remote - ); - $this->repository->flush(); + $this->repository->createDatabase($database->database); + $this->repository->createUser( + $database->username, $database->remote, $this->encrypter->decrypt($database->password), $database->max_connections + ); + $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); + $this->repository->flush(); - $this->database->commit(); - } catch (Exception $ex) { + return $database; + }); + } catch (Exception $exception) { try { - if (isset($database) && $database instanceof Database) { + if ($database instanceof Database) { $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); $this->repository->flush(); } - } catch (Exception $exTwo) { - // ignore an exception + } catch (Exception $exception) { + // Do nothing here. We've already encountered an issue before this point so no + // reason to prioritize this error over the initial one. } - $this->database->rollBack(); - throw $ex; + throw $exception; } - - return $database; } /** diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index b48f9e6f4..734740324 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -6,9 +6,7 @@ use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; -use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException; use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException; -use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; class DeployServerDatabaseService { @@ -49,20 +47,12 @@ class DeployServerDatabaseService * @param array $data * @return \Pterodactyl\Models\Database * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException - * @throws \Exception */ public function handle(Server $server, array $data): Database { - if (! config('pterodactyl.client_features.databases.enabled')) { - throw new DatabaseClientFeatureNotEnabledException; - } - - $databases = $this->repository->findCountWhere([['server_id', '=', $server->id]]); - if (! is_null($server->database_limit) && $databases >= $server->database_limit) { - throw new TooManyDatabasesException; - } - $allowRandom = config('pterodactyl.client_features.databases.allow_random'); $hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([ ['node_id', '=', $server->node_id], @@ -81,7 +71,7 @@ class DeployServerDatabaseService $host = $hosts->random(); - return $this->managementService->create($server->id, [ + return $this->managementService->create($server, [ 'database_host_id' => $host->id, 'database' => array_get($data, 'database'), 'remote' => array_get($data, 'remote'),