diff --git a/app/Http/Controllers/Api/Client/Servers/CommandController.php b/app/Http/Controllers/Api/Client/Servers/CommandController.php index 6cc50de47..6b3a7ab45 100644 --- a/app/Http/Controllers/Api/Client/Servers/CommandController.php +++ b/app/Http/Controllers/Api/Client/Servers/CommandController.php @@ -8,7 +8,6 @@ use Pterodactyl\Facades\Activity; use Psr\Http\Message\ResponseInterface; use GuzzleHttp\Exception\BadResponseException; use Symfony\Component\HttpKernel\Exception\HttpException; -use Pterodactyl\Repositories\Wings\DaemonCommandRepository; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\SendCommandRequest; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; @@ -18,7 +17,7 @@ class CommandController extends ClientApiController /** * CommandController constructor. */ - public function __construct(private DaemonCommandRepository $repository) + public function __construct() { parent::__construct(); } @@ -31,7 +30,7 @@ class CommandController extends ClientApiController public function index(SendCommandRequest $request, Server $server): Response { try { - $this->repository->setServer($server)->send($request->input('command')); + $server->send($request->input('command')); } catch (DaemonConnectionException $exception) { $previous = $exception->getPrevious(); diff --git a/app/Http/Middleware/Api/Client/SubstituteClientBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php index ec30d80fd..44a59e537 100644 --- a/app/Http/Middleware/Api/Client/SubstituteClientBindings.php +++ b/app/Http/Middleware/Api/Client/SubstituteClientBindings.php @@ -3,11 +3,17 @@ namespace Pterodactyl\Http\Middleware\Api\Client; use Closure; +use Illuminate\Contracts\Routing\Registrar; use Pterodactyl\Models\Server; use Illuminate\Routing\Middleware\SubstituteBindings; class SubstituteClientBindings extends SubstituteBindings { + public function __construct(Registrar $router, private Server $server) + { + parent::__construct($router); + } + /** * @param \Illuminate\Http\Request $request */ @@ -16,7 +22,7 @@ class SubstituteClientBindings extends SubstituteBindings // Override default behavior of the model binding to use a specific table // column rather than the default 'id'. $this->router->bind('server', function ($value) { - return Server::query()->where(strlen($value) === 8 ? 'uuidShort' : 'uuid', $value)->firstOrFail(); + return $this->server->query()->where(strlen($value) === 8 ? 'uuidShort' : 'uuid', $value)->firstOrFail(); }); $this->router->bind('user', function ($value, $route) { diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 1e1ab2bb3..058ecb788 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -13,7 +13,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\DispatchesJobs; use Pterodactyl\Services\Backups\InitiateBackupService; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; -use Pterodactyl\Repositories\Wings\DaemonCommandRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class RunTaskJob extends Job implements ShouldQueue @@ -36,7 +35,6 @@ class RunTaskJob extends Job implements ShouldQueue * @throws \Throwable */ public function handle( - DaemonCommandRepository $commandRepository, InitiateBackupService $backupService, DaemonPowerRepository $powerRepository ) { @@ -66,7 +64,7 @@ class RunTaskJob extends Job implements ShouldQueue $powerRepository->setServer($server)->send($this->task->payload); break; case Task::ACTION_COMMAND: - $commandRepository->setServer($server)->send($this->task->payload); + $server->send($this->task->payload); break; case Task::ACTION_BACKUP: $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); diff --git a/app/Models/Server.php b/app/Models/Server.php index 5ad99151a..ce376f470 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,15 +2,20 @@ namespace Pterodactyl\Models; +use Illuminate\Support\Facades\Http; +use Psr\Http\Message\ResponseInterface; use Illuminate\Notifications\Notifiable; +use GuzzleHttp\Exception\GuzzleException; use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; +use GuzzleHttp\Exception\TransferException; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; /** * \Pterodactyl\Models\Server. @@ -378,4 +383,20 @@ class Server extends Model throw new ServerStateConflictException($this); } } + + /** + * Sends a command or multiple commands to a running server instance. + * + * @throws DaemonConnectionException|GuzzleException + */ + public function send(array|string $command): ResponseInterface + { + try { + return Http::daemon($this->node)->post("/api/servers/{$this->uuid}/commands", [ + 'commands' => is_array($command) ? $command : [$command], + ])->toPsrResponse(); + } catch (TransferException $exception) { + throw new DaemonConnectionException($exception); + } + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index d4ffdadbb..86904c236 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,8 +6,10 @@ use View; use Cache; use Pterodactyl\Models; use Illuminate\Support\Str; +use Pterodactyl\Models\Node; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Pterodactyl\Extensions\Themes\Theme; @@ -49,6 +51,17 @@ class AppServiceProvider extends ServiceProvider 'task' => Models\Task::class, 'user' => Models\User::class, ]); + + Http::macro( + 'daemon', + fn (Node $node, array $headers = []) => Http::acceptJson()->withHeaders([ + 'Authorization' => 'Bearer ' . $node->getDecryptedKey(), + ] + $headers) + ->withOptions(['verify' => (bool) app()->environment('production')]) + ->timeout(config('pterodactyl.guzzle.timeout')) + ->connectTimeout(config('pterodactyl.guzzle.connect_timeout')) + ->baseUrl($node->getConnectionAddress()) + ); } /** diff --git a/app/Repositories/Wings/DaemonCommandRepository.php b/app/Repositories/Wings/DaemonCommandRepository.php deleted file mode 100644 index cde29ff36..000000000 --- a/app/Repositories/Wings/DaemonCommandRepository.php +++ /dev/null @@ -1,33 +0,0 @@ -server, Server::class); - - try { - return $this->getHttpClient()->post( - sprintf('/api/servers/%s/commands', $this->server->uuid), - [ - 'json' => ['commands' => is_array($command) ? $command : [$command]], - ] - ); - } catch (TransferException $exception) { - throw new DaemonConnectionException($exception); - } - } -} diff --git a/tests/Integration/Api/Client/Server/CommandControllerTest.php b/tests/Integration/Api/Client/Server/CommandControllerTest.php index 8fa106cfc..96cff4ffe 100644 --- a/tests/Integration/Api/Client/Server/CommandControllerTest.php +++ b/tests/Integration/Api/Client/Server/CommandControllerTest.php @@ -5,11 +5,11 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; use Mockery; use GuzzleHttp\Psr7\Request; use Illuminate\Http\Response; +use Mockery\MockInterface; use Pterodactyl\Models\Server; use Pterodactyl\Models\Permission; use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Psr7\Response as GuzzleResponse; -use Pterodactyl\Repositories\Wings\DaemonCommandRepository; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; @@ -53,12 +53,12 @@ class CommandControllerTest extends ClientApiIntegrationTestCase { [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]); - $mock = $this->mock(DaemonCommandRepository::class); - $mock->expects('setServer') - ->with(Mockery::on(fn (Server $value) => $value->is($server))) - ->andReturnSelf(); + $server = \Mockery::mock($server)->makePartial(); + $server->expects('query->where->firstOrFail')->andReturns($server); - $mock->expects('send')->with('say Test')->andReturn(new GuzzleResponse()); + $this->instance(Server::class, $server); + + $server->expects('send')->with('say Test')->andReturn(new GuzzleResponse()); $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', @@ -75,13 +75,16 @@ class CommandControllerTest extends ClientApiIntegrationTestCase { [$user, $server] = $this->generateTestAccount(); - $mock = $this->mock(DaemonCommandRepository::class); - $mock->expects('setServer->send')->andThrows( + $server = \Mockery::mock($server)->makePartial(); + $server->expects('query->where->firstOrFail')->andReturns($server); + $server->expects('send')->andThrows( new DaemonConnectionException( new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY)) ) ); + $this->instance(Server::class, $server); + $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [ 'command' => 'say Test', ]);