Prevent creating a new database via the application API if server is at its limit; closes #2129
This commit is contained in:
parent
910a48ee8a
commit
b78aa180ea
6 changed files with 116 additions and 76 deletions
|
@ -312,12 +312,12 @@ class ServersController extends Controller
|
||||||
* Creates a new database assigned to a specific server.
|
* Creates a new database assigned to a specific server.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request
|
* @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request
|
||||||
* @param int $server
|
* @param \Pterodactyl\Models\Server $server
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @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, [
|
$this->databaseManagementService->create($server, [
|
||||||
'database' => $request->input('database'),
|
'database' => $request->input('database'),
|
||||||
|
@ -326,7 +326,7 @@ class ServersController extends Controller
|
||||||
'max_connections' => $request->input('max_connections'),
|
'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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -57,13 +57,12 @@ class DatabaseController extends ApplicationApiController
|
||||||
* server.
|
* server.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
|
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
* @return array
|
* @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($server->databases)
|
||||||
|
|
||||||
return $this->fractal->collection($databases)
|
|
||||||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
@ -72,11 +71,13 @@ class DatabaseController extends ApplicationApiController
|
||||||
* Return a single server database.
|
* Return a single server database.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request
|
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
|
* @param \Pterodactyl\Models\Database $database
|
||||||
* @return array
|
* @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))
|
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
@ -85,29 +86,31 @@ class DatabaseController extends ApplicationApiController
|
||||||
* Reset the password for a specific server database.
|
* Reset the password for a specific server database.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
|
* @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
|
* @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.
|
* Create a new database on the Panel for a given server.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request
|
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @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, $request->validated());
|
||||||
$database = $this->databaseManagementService->create($server->id, $request->validated());
|
|
||||||
|
|
||||||
return $this->fractal->item($database)
|
return $this->fractal->item($database)
|
||||||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||||
|
@ -117,7 +120,7 @@ class DatabaseController extends ApplicationApiController
|
||||||
'database' => $database->id,
|
'database' => $database->id,
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
->respond(201);
|
->respond(Response::HTTP_CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -69,9 +69,7 @@ class DatabaseController extends ClientApiController
|
||||||
*/
|
*/
|
||||||
public function index(GetDatabasesRequest $request, Server $server): array
|
public function index(GetDatabasesRequest $request, Server $server): array
|
||||||
{
|
{
|
||||||
$databases = $this->repository->getDatabasesForServer($server->id);
|
return $this->fractal->collection($server->databases)
|
||||||
|
|
||||||
return $this->fractal->collection($databases)
|
|
||||||
->transformWith($this->getTransformer(DatabaseTransformer::class))
|
->transformWith($this->getTransformer(DatabaseTransformer::class))
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
@ -83,6 +81,8 @@ class DatabaseController extends ClientApiController
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @param \Pterodactyl\Models\Server $server
|
||||||
* @return array
|
* @return array
|
||||||
*
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
* @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
|
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
|
||||||
*/
|
*/
|
||||||
public function store(StoreDatabaseRequest $request, Server $server): array
|
public function store(StoreDatabaseRequest $request, Server $server): array
|
||||||
|
|
|
@ -2,6 +2,21 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Models;
|
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
|
class Database extends Model
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,19 +3,22 @@
|
||||||
namespace Pterodactyl\Services\Databases;
|
namespace Pterodactyl\Services\Databases;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Pterodactyl\Models\Server;
|
||||||
use Pterodactyl\Models\Database;
|
use Pterodactyl\Models\Database;
|
||||||
use Pterodactyl\Helpers\Utilities;
|
use Pterodactyl\Helpers\Utilities;
|
||||||
use Illuminate\Database\DatabaseManager;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Illuminate\Contracts\Encryption\Encrypter;
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
use Pterodactyl\Extensions\DynamicDatabaseConnection;
|
use Pterodactyl\Extensions\DynamicDatabaseConnection;
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
||||||
|
use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException;
|
||||||
|
use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;
|
||||||
|
|
||||||
class DatabaseManagementService
|
class DatabaseManagementService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \Illuminate\Database\DatabaseManager
|
* @var \Illuminate\Database\ConnectionInterface
|
||||||
*/
|
*/
|
||||||
private $database;
|
private $connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Extensions\DynamicDatabaseConnection
|
* @var \Pterodactyl\Extensions\DynamicDatabaseConnection
|
||||||
|
@ -33,84 +36,113 @@ class DatabaseManagementService
|
||||||
private $repository;
|
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
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $useRandomHost = false;
|
protected $validateDatabaseLimit = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CreationService constructor.
|
* CreationService constructor.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Database\DatabaseManager $database
|
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||||
* @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic
|
* @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic
|
||||||
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
|
||||||
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
DatabaseManager $database,
|
ConnectionInterface $connection,
|
||||||
DynamicDatabaseConnection $dynamic,
|
DynamicDatabaseConnection $dynamic,
|
||||||
DatabaseRepositoryInterface $repository,
|
DatabaseRepositoryInterface $repository,
|
||||||
Encrypter $encrypter
|
Encrypter $encrypter
|
||||||
) {
|
) {
|
||||||
$this->database = $database;
|
$this->connection = $connection;
|
||||||
$this->dynamic = $dynamic;
|
$this->dynamic = $dynamic;
|
||||||
$this->encrypter = $encrypter;
|
$this->encrypter = $encrypter;
|
||||||
$this->repository = $repository;
|
$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.
|
* Create a new database that is linked to a specific host.
|
||||||
*
|
*
|
||||||
* @param int $server
|
* @param \Pterodactyl\Models\Server $server
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @return \Pterodactyl\Models\Database
|
* @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;
|
if (! config('pterodactyl.client_features.databases.enabled')) {
|
||||||
$data['database'] = sprintf('s%d_%s', $server, $data['database']);
|
throw new DatabaseClientFeatureNotEnabledException;
|
||||||
$data['username'] = sprintf('u%d_%s', $server, str_random(10));
|
}
|
||||||
$data['password'] = $this->encrypter->encrypt(
|
|
||||||
Utilities::randomStringWithSpecialCharacters(24)
|
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 {
|
try {
|
||||||
$database = $this->repository->createIfNotExists($data);
|
return $this->connection->transaction(function () use ($data, &$database) {
|
||||||
$this->dynamic->set('dynamic', $data['database_host_id']);
|
$database = $this->repository->createIfNotExists($data);
|
||||||
|
$this->dynamic->set('dynamic', $data['database_host_id']);
|
||||||
|
|
||||||
$this->repository->createDatabase($database->database);
|
$this->repository->createDatabase($database->database);
|
||||||
$this->repository->createUser(
|
$this->repository->createUser(
|
||||||
$database->username,
|
$database->username, $database->remote, $this->encrypter->decrypt($database->password), $database->max_connections
|
||||||
$database->remote,
|
);
|
||||||
$this->encrypter->decrypt($database->password),
|
$this->repository->assignUserToDatabase($database->database, $database->username, $database->remote);
|
||||||
$database->max_connections
|
$this->repository->flush();
|
||||||
);
|
|
||||||
$this->repository->assignUserToDatabase(
|
|
||||||
$database->database,
|
|
||||||
$database->username,
|
|
||||||
$database->remote
|
|
||||||
);
|
|
||||||
$this->repository->flush();
|
|
||||||
|
|
||||||
$this->database->commit();
|
return $database;
|
||||||
} catch (Exception $ex) {
|
});
|
||||||
|
} catch (Exception $exception) {
|
||||||
try {
|
try {
|
||||||
if (isset($database) && $database instanceof Database) {
|
if ($database instanceof Database) {
|
||||||
$this->repository->dropDatabase($database->database);
|
$this->repository->dropDatabase($database->database);
|
||||||
$this->repository->dropUser($database->username, $database->remote);
|
$this->repository->dropUser($database->username, $database->remote);
|
||||||
$this->repository->flush();
|
$this->repository->flush();
|
||||||
}
|
}
|
||||||
} catch (Exception $exTwo) {
|
} catch (Exception $exception) {
|
||||||
// ignore an 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 $exception;
|
||||||
throw $ex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,9 +6,7 @@ use Pterodactyl\Models\Server;
|
||||||
use Pterodactyl\Models\Database;
|
use Pterodactyl\Models\Database;
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
|
||||||
use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException;
|
|
||||||
use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException;
|
use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException;
|
||||||
use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;
|
|
||||||
|
|
||||||
class DeployServerDatabaseService
|
class DeployServerDatabaseService
|
||||||
{
|
{
|
||||||
|
@ -49,20 +47,12 @@ class DeployServerDatabaseService
|
||||||
* @param array $data
|
* @param array $data
|
||||||
* @return \Pterodactyl\Models\Database
|
* @return \Pterodactyl\Models\Database
|
||||||
*
|
*
|
||||||
|
* @throws \Throwable
|
||||||
|
* @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
|
||||||
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
|
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
public function handle(Server $server, array $data): Database
|
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');
|
$allowRandom = config('pterodactyl.client_features.databases.allow_random');
|
||||||
$hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([
|
$hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([
|
||||||
['node_id', '=', $server->node_id],
|
['node_id', '=', $server->node_id],
|
||||||
|
@ -81,7 +71,7 @@ class DeployServerDatabaseService
|
||||||
|
|
||||||
$host = $hosts->random();
|
$host = $hosts->random();
|
||||||
|
|
||||||
return $this->managementService->create($server->id, [
|
return $this->managementService->create($server, [
|
||||||
'database_host_id' => $host->id,
|
'database_host_id' => $host->id,
|
||||||
'database' => array_get($data, 'database'),
|
'database' => array_get($data, 'database'),
|
||||||
'remote' => array_get($data, 'remote'),
|
'remote' => array_get($data, 'remote'),
|
||||||
|
|
Loading…
Reference in a new issue