Compare commits

...

5 commits

Author SHA1 Message Date
Lance Pioch
0b8b6a5272 Update the rest of the logic 2022-10-22 03:05:45 -04:00
Lance Pioch
2c72fdc716 Update tests 2022-10-22 02:59:38 -04:00
Lance Pioch
bff1dfd169 Make this mockable 2022-10-22 02:59:18 -04:00
Lance Pioch
57288ff6c8 Don't need json key anymore 2022-10-21 17:31:52 -04:00
Lance Pioch
264df1876b Add http macro 2022-10-21 16:34:26 -04:00
7 changed files with 55 additions and 48 deletions

View file

@ -8,7 +8,6 @@ use Pterodactyl\Facades\Activity;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Exception\BadResponseException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\SendCommandRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\SendCommandRequest;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
@ -18,7 +17,7 @@ class CommandController extends ClientApiController
/** /**
* CommandController constructor. * CommandController constructor.
*/ */
public function __construct(private DaemonCommandRepository $repository) public function __construct()
{ {
parent::__construct(); parent::__construct();
} }
@ -31,7 +30,7 @@ class CommandController extends ClientApiController
public function index(SendCommandRequest $request, Server $server): Response public function index(SendCommandRequest $request, Server $server): Response
{ {
try { try {
$this->repository->setServer($server)->send($request->input('command')); $server->send($request->input('command'));
} catch (DaemonConnectionException $exception) { } catch (DaemonConnectionException $exception) {
$previous = $exception->getPrevious(); $previous = $exception->getPrevious();

View file

@ -3,11 +3,17 @@
namespace Pterodactyl\Http\Middleware\Api\Client; namespace Pterodactyl\Http\Middleware\Api\Client;
use Closure; use Closure;
use Illuminate\Contracts\Routing\Registrar;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Routing\Middleware\SubstituteBindings;
class SubstituteClientBindings extends SubstituteBindings class SubstituteClientBindings extends SubstituteBindings
{ {
public function __construct(Registrar $router, private Server $server)
{
parent::__construct($router);
}
/** /**
* @param \Illuminate\Http\Request $request * @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 // Override default behavior of the model binding to use a specific table
// column rather than the default 'id'. // column rather than the default 'id'.
$this->router->bind('server', function ($value) { $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) { $this->router->bind('user', function ($value, $route) {

View file

@ -13,7 +13,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Pterodactyl\Services\Backups\InitiateBackupService; use Pterodactyl\Services\Backups\InitiateBackupService;
use Pterodactyl\Repositories\Wings\DaemonPowerRepository; use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class RunTaskJob extends Job implements ShouldQueue class RunTaskJob extends Job implements ShouldQueue
@ -36,7 +35,6 @@ class RunTaskJob extends Job implements ShouldQueue
* @throws \Throwable * @throws \Throwable
*/ */
public function handle( public function handle(
DaemonCommandRepository $commandRepository,
InitiateBackupService $backupService, InitiateBackupService $backupService,
DaemonPowerRepository $powerRepository DaemonPowerRepository $powerRepository
) { ) {
@ -66,7 +64,7 @@ class RunTaskJob extends Job implements ShouldQueue
$powerRepository->setServer($server)->send($this->task->payload); $powerRepository->setServer($server)->send($this->task->payload);
break; break;
case Task::ACTION_COMMAND: case Task::ACTION_COMMAND:
$commandRepository->setServer($server)->send($this->task->payload); $server->send($this->task->payload);
break; break;
case Task::ACTION_BACKUP: case Task::ACTION_BACKUP:
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true);

View file

@ -2,15 +2,20 @@
namespace Pterodactyl\Models; namespace Pterodactyl\Models;
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
use Znck\Eloquent\Traits\BelongsToThrough; use Znck\Eloquent\Traits\BelongsToThrough;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
/** /**
* \Pterodactyl\Models\Server. * \Pterodactyl\Models\Server.
@ -378,4 +383,20 @@ class Server extends Model
throw new ServerStateConflictException($this); 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);
}
}
} }

View file

@ -6,8 +6,10 @@ use View;
use Cache; use Cache;
use Pterodactyl\Models; use Pterodactyl\Models;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Pterodactyl\Models\Node;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Illuminate\Pagination\Paginator; use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Pterodactyl\Extensions\Themes\Theme; use Pterodactyl\Extensions\Themes\Theme;
@ -49,6 +51,17 @@ class AppServiceProvider extends ServiceProvider
'task' => Models\Task::class, 'task' => Models\Task::class,
'user' => Models\User::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())
);
} }
/** /**

View file

@ -1,33 +0,0 @@
<?php
namespace Pterodactyl\Repositories\Wings;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\TransferException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class DaemonCommandRepository extends DaemonRepository
{
/**
* Sends a command or multiple commands to a running server instance.
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function send(array|string $command): ResponseInterface
{
Assert::isInstanceOf($this->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);
}
}
}

View file

@ -5,11 +5,11 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server;
use Mockery; use Mockery;
use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Mockery\MockInterface;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Permission; use Pterodactyl\Models\Permission;
use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Response as GuzzleResponse; use GuzzleHttp\Psr7\Response as GuzzleResponse;
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
@ -53,12 +53,12 @@ class CommandControllerTest extends ClientApiIntegrationTestCase
{ {
[$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]); [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]);
$mock = $this->mock(DaemonCommandRepository::class); $server = \Mockery::mock($server)->makePartial();
$mock->expects('setServer') $server->expects('query->where->firstOrFail')->andReturns($server);
->with(Mockery::on(fn (Server $value) => $value->is($server)))
->andReturnSelf();
$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", [ $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [
'command' => 'say Test', 'command' => 'say Test',
@ -75,13 +75,16 @@ class CommandControllerTest extends ClientApiIntegrationTestCase
{ {
[$user, $server] = $this->generateTestAccount(); [$user, $server] = $this->generateTestAccount();
$mock = $this->mock(DaemonCommandRepository::class); $server = \Mockery::mock($server)->makePartial();
$mock->expects('setServer->send')->andThrows( $server->expects('query->where->firstOrFail')->andReturns($server);
$server->expects('send')->andThrows(
new DaemonConnectionException( new DaemonConnectionException(
new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY)) 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", [ $response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/command", [
'command' => 'say Test', 'command' => 'say Test',
]); ]);