Add server database management support to API.
This commit is contained in:
parent
2bd691efad
commit
de07b3cc7f
10 changed files with 257 additions and 54 deletions
|
@ -46,6 +46,14 @@ class DisplayException extends PterodactylException
|
|||
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
|
||||
* and then redirecting them back to the page that they came from. If the
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
|
||||
|
||||
use League\Fractal\TransformerAbstract;
|
||||
use Spatie\Fractal\Fractal as SpatieFractal;
|
||||
use League\Fractal\Serializer\JsonApiSerializer;
|
||||
use Spatie\Fractalistic\Fractal as SpatieFractal;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
|
||||
|
|
|
@ -2,13 +2,32 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
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\Transformers\Api\Application\ServerDatabaseTransformer;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
|
||||
*/
|
||||
private $databaseManagementService;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Databases\DatabasePasswordService
|
||||
*/
|
||||
private $databasePasswordService;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
|
||||
*/
|
||||
|
@ -17,12 +36,19 @@ class DatabaseController extends ApplicationApiController
|
|||
/**
|
||||
* DatabaseController constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService
|
||||
* @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService
|
||||
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(DatabaseRepositoryInterface $repository)
|
||||
{
|
||||
public function __construct(
|
||||
DatabaseManagementService $databaseManagementService,
|
||||
DatabasePasswordService $databasePasswordService,
|
||||
DatabaseRepositoryInterface $repository
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->databaseManagementService = $databaseManagementService;
|
||||
$this->databasePasswordService = $databasePasswordService;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
@ -30,10 +56,11 @@ class DatabaseController extends ApplicationApiController
|
|||
* Return a listing of all databases currently available to a single
|
||||
* server.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return array
|
||||
*/
|
||||
public function index(Server $server): array
|
||||
public function index(GetServerDatabasesRequest $request, Server $server): array
|
||||
{
|
||||
$databases = $this->repository->getDatabasesForServer($server->id);
|
||||
|
||||
|
@ -41,4 +68,73 @@ class DatabaseController extends ApplicationApiController
|
|||
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
|
||||
->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;
|
||||
|
||||
use Closure;
|
||||
use ReflectionMethod;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Routing\ImplicitRouteBinding;
|
||||
use Illuminate\Contracts\Routing\UrlRoutable;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class ApiSubstituteBindings extends SubstituteBindings
|
||||
{
|
||||
|
@ -24,47 +21,18 @@ class ApiSubstituteBindings extends SubstituteBindings
|
|||
$route = $request->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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Determine if the request passes the authorization check as well
|
||||
* as the exists check.
|
||||
*
|
||||
|
@ -81,18 +81,24 @@ abstract class ApplicationApiRequest extends FormRequest
|
|||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
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
|
||||
// authenticated to access the endpoint. This avoids exposing that
|
||||
// an item exists (or does not exist) to the user until they can prove
|
||||
// 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.');
|
||||
}
|
||||
|
||||
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::post('/', 'Servers\DatabaseController@store');
|
||||
Route::patch('/{database}', 'Servers\DatabaseController@update');
|
||||
Route::post('/{database}/reset-password', 'Servers\DatabaseController@resetPassword');
|
||||
|
||||
Route::delete('/{database}', 'Servers\DatabaseController@delete');
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue