Add server database management support to API.
This commit is contained in:
parent
2bd691efad
commit
de07b3cc7f
|
@ -46,6 +46,14 @@ class DisplayException extends PterodactylException
|
||||||
return $this->level;
|
return $this->level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getStatusCode()
|
||||||
|
{
|
||||||
|
return Response::HTTP_BAD_REQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the exception to the user by adding a flashed message to the session
|
* Render the exception to the user by adding a flashed message to the session
|
||||||
* and then redirecting them back to the page that they came from. If the
|
* and then redirecting them back to the page that they came from. If the
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
|
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
|
||||||
|
|
||||||
use League\Fractal\TransformerAbstract;
|
use League\Fractal\TransformerAbstract;
|
||||||
|
use Spatie\Fractal\Fractal as SpatieFractal;
|
||||||
use League\Fractal\Serializer\JsonApiSerializer;
|
use League\Fractal\Serializer\JsonApiSerializer;
|
||||||
use Spatie\Fractalistic\Fractal as SpatieFractal;
|
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,32 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
|
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Response;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
|
use Pterodactyl\Models\Database;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Pterodactyl\Services\Databases\DatabasePasswordService;
|
||||||
|
use Pterodactyl\Services\Databases\DatabaseManagementService;
|
||||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
||||||
use Pterodactyl\Transformers\Api\Application\ServerDatabaseTransformer;
|
use Pterodactyl\Transformers\Api\Application\ServerDatabaseTransformer;
|
||||||
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest;
|
||||||
|
|
||||||
class DatabaseController extends ApplicationApiController
|
class DatabaseController extends ApplicationApiController
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
|
||||||
|
*/
|
||||||
|
private $databaseManagementService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Databases\DatabasePasswordService
|
||||||
|
*/
|
||||||
|
private $databasePasswordService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
|
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
|
||||||
*/
|
*/
|
||||||
|
@ -17,12 +36,19 @@ class DatabaseController extends ApplicationApiController
|
||||||
/**
|
/**
|
||||||
* DatabaseController constructor.
|
* DatabaseController constructor.
|
||||||
*
|
*
|
||||||
|
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService
|
||||||
|
* @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService
|
||||||
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
|
||||||
*/
|
*/
|
||||||
public function __construct(DatabaseRepositoryInterface $repository)
|
public function __construct(
|
||||||
{
|
DatabaseManagementService $databaseManagementService,
|
||||||
|
DatabasePasswordService $databasePasswordService,
|
||||||
|
DatabaseRepositoryInterface $repository
|
||||||
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->databaseManagementService = $databaseManagementService;
|
||||||
|
$this->databasePasswordService = $databasePasswordService;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,10 +56,11 @@ class DatabaseController extends ApplicationApiController
|
||||||
* Return a listing of all databases currently available to a single
|
* Return a listing of all databases currently available to a single
|
||||||
* server.
|
* server.
|
||||||
*
|
*
|
||||||
|
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
|
||||||
* @param \Pterodactyl\Models\Server $server
|
* @param \Pterodactyl\Models\Server $server
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function index(Server $server): array
|
public function index(GetServerDatabasesRequest $request, Server $server): array
|
||||||
{
|
{
|
||||||
$databases = $this->repository->getDatabasesForServer($server->id);
|
$databases = $this->repository->getDatabasesForServer($server->id);
|
||||||
|
|
||||||
|
@ -41,4 +68,73 @@ class DatabaseController extends ApplicationApiController
|
||||||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, Server $server, Database $database): array
|
||||||
|
{
|
||||||
|
return $this->fractal->item($database)
|
||||||
|
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the password for a specific server database.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
|
* @param \Pterodactyl\Models\Database $database
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response
|
||||||
|
{
|
||||||
|
$this->databasePasswordService->handle($database, str_random(24));
|
||||||
|
|
||||||
|
return response('', 204);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 \Pterodactyl\Exceptions\DisplayException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
*/
|
||||||
|
public function store(StoreServerDatabaseRequest $request, Server $server): JsonResponse
|
||||||
|
{
|
||||||
|
$database = $this->databaseManagementService->create($server->id, $request->validated());
|
||||||
|
|
||||||
|
return $this->fractal->item($database)
|
||||||
|
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||||
|
->respond(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a specific database from the Panel.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
|
* @param \Pterodactyl\Models\Database $database
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response
|
||||||
|
{
|
||||||
|
$this->databaseManagementService->delete($database->id);
|
||||||
|
|
||||||
|
return response('', 204);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,8 @@
|
||||||
namespace Pterodactyl\Http\Middleware\Api;
|
namespace Pterodactyl\Http\Middleware\Api;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use ReflectionMethod;
|
|
||||||
use Illuminate\Container\Container;
|
|
||||||
use Illuminate\Routing\ImplicitRouteBinding;
|
|
||||||
use Illuminate\Contracts\Routing\UrlRoutable;
|
|
||||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
|
||||||
class ApiSubstituteBindings extends SubstituteBindings
|
class ApiSubstituteBindings extends SubstituteBindings
|
||||||
{
|
{
|
||||||
|
@ -24,47 +21,18 @@ class ApiSubstituteBindings extends SubstituteBindings
|
||||||
$route = $request->route();
|
$route = $request->route();
|
||||||
|
|
||||||
$this->router->substituteBindings($route);
|
$this->router->substituteBindings($route);
|
||||||
$this->resolveForRoute($route);
|
|
||||||
|
// Attempt to resolve bindings for this route. If one of the models
|
||||||
|
// cannot be resolved do not immediately return a 404 error. Set a request
|
||||||
|
// attribute that can be checked in the base API request class to only
|
||||||
|
// trigger a 404 after validating that the API key making the request is valid
|
||||||
|
// and even has permission to access the requested resource.
|
||||||
|
try {
|
||||||
|
$this->router->substituteImplicitBindings($route);
|
||||||
|
} catch (ModelNotFoundException $exception) {
|
||||||
|
$request->attributes->set('is_missing_model', true);
|
||||||
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the implicit route bindings for the given route. This function
|
|
||||||
* overrides Laravel's default inn \Illuminate\Routing\ImplictRouteBinding
|
|
||||||
* to not throw a 404 error when a model is not found.
|
|
||||||
*
|
|
||||||
* If a model is not found using the provided information, the binding is
|
|
||||||
* replaced with null which is then checked in the form requests on API
|
|
||||||
* routes. This avoids a potential imformation leak on API routes for
|
|
||||||
* unauthenticated users.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Routing\Route $route
|
|
||||||
*/
|
|
||||||
protected function resolveForRoute($route)
|
|
||||||
{
|
|
||||||
$parameters = $route->parameters();
|
|
||||||
|
|
||||||
// Avoid having to copy and paste the entirety of that class into this middleware
|
|
||||||
// by using reflection to access a protected method.
|
|
||||||
$reflection = new ReflectionMethod(ImplicitRouteBinding::class, 'getParameterName');
|
|
||||||
$reflection->setAccessible(true);
|
|
||||||
|
|
||||||
foreach ($route->signatureParameters(UrlRoutable::class) as $parameter) {
|
|
||||||
if (! $parameterName = $reflection->invokeArgs(null, [$parameter->name, $parameters])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$parameterValue = $parameters[$parameterName];
|
|
||||||
|
|
||||||
if ($parameterValue instanceof UrlRoutable) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find an existing model, if one is not found simply bind the
|
|
||||||
// parameter as null.
|
|
||||||
$instance = Container::getInstance()->make($parameter->getClass()->name);
|
|
||||||
$route->setParameter($parameterName, $instance->resolveRouteBinding($parameterValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ abstract class ApplicationApiRequest extends FormRequest
|
||||||
return $this->attributes->get('api_key');
|
return $this->attributes->get('api_key');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Determine if the request passes the authorization check as well
|
* Determine if the request passes the authorization check as well
|
||||||
* as the exists check.
|
* as the exists check.
|
||||||
*
|
*
|
||||||
|
@ -81,18 +81,24 @@ abstract class ApplicationApiRequest extends FormRequest
|
||||||
*
|
*
|
||||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
protected function passesAuthorization()
|
protected function passesAuthorization()
|
||||||
{
|
{
|
||||||
$passes = parent::passesAuthorization();
|
if (! parent::passesAuthorization()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Only let the user know that a resource does not exist if they are
|
// Only let the user know that a resource does not exist if they are
|
||||||
// authenticated to access the endpoint. This avoids exposing that
|
// authenticated to access the endpoint. This avoids exposing that
|
||||||
// an item exists (or does not exist) to the user until they can prove
|
// an item exists (or does not exist) to the user until they can prove
|
||||||
// that they have permission to know about it.
|
// that they have permission to know about it.
|
||||||
if ($passes && ! $this->resourceExists()) {
|
if ($this->attributes->get('is_missing_model', false) || ! $this->resourceExists()) {
|
||||||
throw new NotFoundHttpException('The requested resource does not exist on this server.');
|
throw new NotFoundHttpException('The requested resource does not exist on this server.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $passes;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
|
||||||
|
|
||||||
|
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||||
|
|
||||||
|
class GetServerDatabaseRequest extends ApplicationApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $permission = AdminAcl::READ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the requested server database exists.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function resourceExists(): bool
|
||||||
|
{
|
||||||
|
$server = $this->route()->parameter('server');
|
||||||
|
$database = $this->route()->parameter('database');
|
||||||
|
|
||||||
|
return $database->server_id === $server->id;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
|
||||||
|
|
||||||
|
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||||
|
|
||||||
|
class GetServerDatabasesRequest extends ApplicationApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $permission = AdminAcl::READ;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
|
||||||
|
|
||||||
|
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
||||||
|
|
||||||
|
class ServerDatabaseWriteRequest extends GetServerDatabasesRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $permission = AdminAcl::WRITE;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
|
||||||
|
|
||||||
|
use Pterodactyl\Services\Acl\Api\AdminAcl;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||||
|
|
||||||
|
class StoreServerDatabaseRequest extends ApplicationApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $permission = AdminAcl::WRITE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation rules for database creation.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'database' => 'required|string|min:1|max:24',
|
||||||
|
'remote' => 'required|string|min:1',
|
||||||
|
'host' => 'required|integer|exists:database_hosts,id',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return data formatted in the correct format for the service to consume.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validated()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'database' => $this->input('database'),
|
||||||
|
'remote' => $this->input('remote'),
|
||||||
|
'database_host_id' => $this->input('host'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format error messages in a more understandable format for API output.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function attributes()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'host' => 'Database Host Server ID',
|
||||||
|
'remote' => 'Remote Connection String',
|
||||||
|
'database' => 'Database Name',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -89,7 +89,7 @@ Route::group(['prefix' => '/servers'], function () {
|
||||||
Route::get('/{database}', 'Servers\DatabaseController@view')->name('api.application.servers.databases.view');
|
Route::get('/{database}', 'Servers\DatabaseController@view')->name('api.application.servers.databases.view');
|
||||||
|
|
||||||
Route::post('/', 'Servers\DatabaseController@store');
|
Route::post('/', 'Servers\DatabaseController@store');
|
||||||
Route::patch('/{database}', 'Servers\DatabaseController@update');
|
Route::post('/{database}/reset-password', 'Servers\DatabaseController@resetPassword');
|
||||||
|
|
||||||
Route::delete('/{database}', 'Servers\DatabaseController@delete');
|
Route::delete('/{database}', 'Servers\DatabaseController@delete');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue