Skip to content

Commit b78aa18

Browse files
committed
Prevent creating a new database via the application API if server is at its limit; closes pterodactyl#2129
1 parent 910a48e commit b78aa18

File tree

6 files changed

+119
-79
lines changed

6 files changed

+119
-79
lines changed

app/Http/Controllers/Admin/ServersController.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,12 @@ public function saveStartup(Request $request, Server $server)
312312
* Creates a new database assigned to a specific server.
313313
*
314314
* @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request
315-
* @param int $server
315+
* @param \Pterodactyl\Models\Server $server
316316
* @return \Illuminate\Http\RedirectResponse
317317
*
318-
* @throws \Exception
318+
* @throws \Throwable
319319
*/
320-
public function newDatabase(StoreServerDatabaseRequest $request, $server)
320+
public function newDatabase(StoreServerDatabaseRequest $request, Server $server)
321321
{
322322
$this->databaseManagementService->create($server, [
323323
'database' => $request->input('database'),
@@ -326,7 +326,7 @@ public function newDatabase(StoreServerDatabaseRequest $request, $server)
326326
'max_connections' => $request->input('max_connections'),
327327
]);
328328

329-
return redirect()->route('admin.servers.view.database', $server)->withInput();
329+
return redirect()->route('admin.servers.view.database', $server->id)->withInput();
330330
}
331331

332332
/**

app/Http/Controllers/Api/Application/Servers/DatabaseController.php

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,12 @@ public function __construct(
5757
* server.
5858
*
5959
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
60+
* @param \Pterodactyl\Models\Server $server
6061
* @return array
6162
*/
62-
public function index(GetServerDatabasesRequest $request): array
63+
public function index(GetServerDatabasesRequest $request, Server $server): array
6364
{
64-
$databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id);
65-
66-
return $this->fractal->collection($databases)
65+
return $this->fractal->collection($server->databases)
6766
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
6867
->toArray();
6968
}
@@ -72,11 +71,13 @@ public function index(GetServerDatabasesRequest $request): array
7271
* Return a single server database.
7372
*
7473
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request
74+
* @param \Pterodactyl\Models\Server $server
75+
* @param \Pterodactyl\Models\Database $database
7576
* @return array
7677
*/
77-
public function view(GetServerDatabaseRequest $request): array
78+
public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array
7879
{
79-
return $this->fractal->item($request->getModel(Database::class))
80+
return $this->fractal->item($database)
8081
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
8182
->toArray();
8283
}
@@ -85,29 +86,31 @@ public function view(GetServerDatabaseRequest $request): array
8586
* Reset the password for a specific server database.
8687
*
8788
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
88-
* @return \Illuminate\Http\Response
89+
* @param \Pterodactyl\Models\Server $server
90+
* @param \Pterodactyl\Models\Database $database
91+
* @return \Illuminate\Http\JsonResponse
8992
*
9093
* @throws \Throwable
9194
*/
92-
public function resetPassword(ServerDatabaseWriteRequest $request): Response
95+
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse
9396
{
94-
$this->databasePasswordService->handle($request->getModel(Database::class));
97+
$this->databasePasswordService->handle($database);
9598

96-
return response('', 204);
99+
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
97100
}
98101

99102
/**
100103
* Create a new database on the Panel for a given server.
101104
*
102105
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request
106+
* @param \Pterodactyl\Models\Server $server
103107
* @return \Illuminate\Http\JsonResponse
104108
*
105-
* @throws \Exception
109+
* @throws \Throwable
106110
*/
107-
public function store(StoreServerDatabaseRequest $request): JsonResponse
111+
public function store(StoreServerDatabaseRequest $request, Server $server): JsonResponse
108112
{
109-
$server = $request->getModel(Server::class);
110-
$database = $this->databaseManagementService->create($server->id, $request->validated());
113+
$database = $this->databaseManagementService->create($server, $request->validated());
111114

112115
return $this->fractal->item($database)
113116
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
@@ -117,7 +120,7 @@ public function store(StoreServerDatabaseRequest $request): JsonResponse
117120
'database' => $database->id,
118121
]),
119122
])
120-
->respond(201);
123+
->respond(Response::HTTP_CREATED);
121124
}
122125

123126
/**

app/Http/Controllers/Api/Client/Servers/DatabaseController.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ public function __construct(
6969
*/
7070
public function index(GetDatabasesRequest $request, Server $server): array
7171
{
72-
$databases = $this->repository->getDatabasesForServer($server->id);
73-
74-
return $this->fractal->collection($databases)
72+
return $this->fractal->collection($server->databases)
7573
->transformWith($this->getTransformer(DatabaseTransformer::class))
7674
->toArray();
7775
}
@@ -83,6 +81,8 @@ public function index(GetDatabasesRequest $request, Server $server): array
8381
* @param \Pterodactyl\Models\Server $server
8482
* @return array
8583
*
84+
* @throws \Throwable
85+
* @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
8686
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
8787
*/
8888
public function store(StoreDatabaseRequest $request, Server $server): array

app/Models/Database.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
namespace Pterodactyl\Models;
44

5+
/**
6+
* @property int $id
7+
* @property int $server_id
8+
* @property int $database_host_id
9+
* @property string $database
10+
* @property string $username
11+
* @property string $remote
12+
* @property string $password
13+
* @property int $max_connections
14+
* @property \Carbon\Carbon $created_at
15+
* @property \Carbon\Carbon $updated_at
16+
*
17+
* @property \Pterodactyl\Models\Server $server
18+
* @property \Pterodactyl\Models\DatabaseHost $host
19+
*/
520
class Database extends Model
621
{
722
/**

app/Services/Databases/DatabaseManagementService.php

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@
33
namespace Pterodactyl\Services\Databases;
44

55
use Exception;
6+
use Pterodactyl\Models\Server;
67
use Pterodactyl\Models\Database;
78
use Pterodactyl\Helpers\Utilities;
8-
use Illuminate\Database\DatabaseManager;
9+
use Illuminate\Database\ConnectionInterface;
910
use Illuminate\Contracts\Encryption\Encrypter;
1011
use Pterodactyl\Extensions\DynamicDatabaseConnection;
1112
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
13+
use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException;
14+
use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;
1215

1316
class DatabaseManagementService
1417
{
1518
/**
16-
* @var \Illuminate\Database\DatabaseManager
19+
* @var \Illuminate\Database\ConnectionInterface
1720
*/
18-
private $database;
21+
private $connection;
1922

2023
/**
2124
* @var \Pterodactyl\Extensions\DynamicDatabaseConnection
@@ -33,84 +36,113 @@ class DatabaseManagementService
3336
private $repository;
3437

3538
/**
39+
* Determines if the service should validate the user's ability to create an additional
40+
* database for this server. In almost all cases this should be true, but to keep things
41+
* flexible you can also set it to false and create more databases than the server is
42+
* allocated.
43+
*
3644
* @var bool
3745
*/
38-
protected $useRandomHost = false;
46+
protected $validateDatabaseLimit = true;
3947

4048
/**
4149
* CreationService constructor.
4250
*
43-
* @param \Illuminate\Database\DatabaseManager $database
51+
* @param \Illuminate\Database\ConnectionInterface $connection
4452
* @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic
4553
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
4654
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
4755
*/
4856
public function __construct(
49-
DatabaseManager $database,
57+
ConnectionInterface $connection,
5058
DynamicDatabaseConnection $dynamic,
5159
DatabaseRepositoryInterface $repository,
5260
Encrypter $encrypter
5361
) {
54-
$this->database = $database;
62+
$this->connection = $connection;
5563
$this->dynamic = $dynamic;
5664
$this->encrypter = $encrypter;
5765
$this->repository = $repository;
5866
}
5967

68+
/**
69+
* Set wether or not this class should validate that the server has enough slots
70+
* left before creating the new database.
71+
*
72+
* @param bool $validate
73+
* @return $this
74+
*/
75+
public function setValidateDatabaseLimit(bool $validate): self
76+
{
77+
$this->validateDatabaseLimit = $validate;
78+
79+
return $this;
80+
}
81+
6082
/**
6183
* Create a new database that is linked to a specific host.
6284
*
63-
* @param int $server
85+
* @param \Pterodactyl\Models\Server $server
6486
* @param array $data
6587
* @return \Pterodactyl\Models\Database
6688
*
67-
* @throws \Exception
89+
* @throws \Throwable
90+
* @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
91+
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
6892
*/
69-
public function create($server, array $data)
93+
public function create(Server $server, array $data)
7094
{
71-
$data['server_id'] = $server;
72-
$data['database'] = sprintf('s%d_%s', $server, $data['database']);
73-
$data['username'] = sprintf('u%d_%s', $server, str_random(10));
74-
$data['password'] = $this->encrypter->encrypt(
75-
Utilities::randomStringWithSpecialCharacters(24)
76-
);
77-
78-
$this->database->beginTransaction();
95+
if (! config('pterodactyl.client_features.databases.enabled')) {
96+
throw new DatabaseClientFeatureNotEnabledException;
97+
}
98+
99+
if ($this->validateDatabaseLimit) {
100+
// If the server has a limit assigned and we've already reached that limit, throw back
101+
// an exception and kill the process.
102+
if (! is_null($server->database_limit) && $server->databases()->count() >= $server->database_limit) {
103+
throw new TooManyDatabasesException;
104+
}
105+
}
106+
107+
$data = array_merge($data, [
108+
'server_id' => $server->id,
109+
'database' => sprintf('s%d_%s', $server->id, $data['database']),
110+
'username' => sprintf('u%d_%s', $server->id, str_random(10)),
111+
'password' => $this->encrypter->encrypt(
112+
Utilities::randomStringWithSpecialCharacters(24)
113+
),
114+
]);
115+
116+
$database = null;
117+
79118
try {
80-
$database = $this->repository->createIfNotExists($data);
81-
$this->dynamic->set('dynamic', $data['database_host_id']);
82-
83-
$this->repository->createDatabase($database->database);
84-
$this->repository->createUser(
85-
$database->username,
86-
$database->remote,
87-
$this->encrypter->decrypt($database->password),
88-
$database->max_connections
89-
);
90-
$this->repository->assignUserToDatabase(
91-
$database->database,
92-
$database->username,
93-
$database->remote
94-
);
95-
$this->repository->flush();
96-
97-
$this->database->commit();
98-
} catch (Exception $ex) {
119+
return $this->connection->transaction(function () use ($data, &$database) {
120+
$database = $this->repository->createIfNotExists($data);
121+
$this->dynamic->set('dynamic', $data['database_host_id']);
122+
123+
$this->repository->createDatabase($database->database);
124+
$this->repository->createUser(
125+
$database->username, $database->remote, $this->encrypter->decrypt($database->password), $database->max_connections
126+
);
127+
$this->repository->assignUserToDatabase($database->database, $database->username, $database->remote);
128+
$this->repository->flush();
129+
130+
return $database;
131+
});
132+
} catch (Exception $exception) {
99133
try {
100-
if (isset($database) && $database instanceof Database) {
134+
if ($database instanceof Database) {
101135
$this->repository->dropDatabase($database->database);
102136
$this->repository->dropUser($database->username, $database->remote);
103137
$this->repository->flush();
104138
}
105-
} catch (Exception $exTwo) {
106-
// ignore an exception
139+
} catch (Exception $exception) {
140+
// Do nothing here. We've already encountered an issue before this point so no
141+
// reason to prioritize this error over the initial one.
107142
}
108143

109-
$this->database->rollBack();
110-
throw $ex;
144+
throw $exception;
111145
}
112-
113-
return $database;
114146
}
115147

116148
/**

app/Services/Databases/DeployServerDatabaseService.php

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
use Pterodactyl\Models\Database;
77
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
88
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
9-
use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException;
109
use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException;
11-
use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;
1210

1311
class DeployServerDatabaseService
1412
{
@@ -49,20 +47,12 @@ public function __construct(
4947
* @param array $data
5048
* @return \Pterodactyl\Models\Database
5149
*
50+
* @throws \Throwable
51+
* @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
5252
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
53-
* @throws \Exception
5453
*/
5554
public function handle(Server $server, array $data): Database
5655
{
57-
if (! config('pterodactyl.client_features.databases.enabled')) {
58-
throw new DatabaseClientFeatureNotEnabledException;
59-
}
60-
61-
$databases = $this->repository->findCountWhere([['server_id', '=', $server->id]]);
62-
if (! is_null($server->database_limit) && $databases >= $server->database_limit) {
63-
throw new TooManyDatabasesException;
64-
}
65-
6656
$allowRandom = config('pterodactyl.client_features.databases.allow_random');
6757
$hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([
6858
['node_id', '=', $server->node_id],
@@ -81,7 +71,7 @@ public function handle(Server $server, array $data): Database
8171

8272
$host = $hosts->random();
8373

84-
return $this->managementService->create($server->id, [
74+
return $this->managementService->create($server, [
8575
'database_host_id' => $host->id,
8676
'database' => array_get($data, 'database'),
8777
'remote' => array_get($data, 'remote'),

0 commit comments

Comments
 (0)