2017-10-30 02:40:34 +00:00
|
|
|
<?php
|
|
|
|
|
2021-01-23 20:09:16 +00:00
|
|
|
namespace Pterodactyl\Tests\Unit\Http\Middleware\Api\Daemon;
|
2017-10-30 02:40:34 +00:00
|
|
|
|
|
|
|
use Mockery as m;
|
|
|
|
use Pterodactyl\Models\Node;
|
2020-06-24 04:59:37 +00:00
|
|
|
use Illuminate\Contracts\Encryption\Encrypter;
|
|
|
|
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
2017-10-30 02:40:34 +00:00
|
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
2018-01-13 22:06:19 +00:00
|
|
|
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
|
2021-01-23 20:09:16 +00:00
|
|
|
use Pterodactyl\Tests\Unit\Http\Middleware\MiddlewareTestCase;
|
2020-06-24 04:59:37 +00:00
|
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
|
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
2017-10-30 02:40:34 +00:00
|
|
|
|
2017-11-02 01:45:43 +00:00
|
|
|
class DaemonAuthenticateTest extends MiddlewareTestCase
|
2017-10-30 02:40:34 +00:00
|
|
|
{
|
|
|
|
/**
|
2020-06-24 04:59:37 +00:00
|
|
|
* @var \Mockery\MockInterface
|
2017-10-30 02:40:34 +00:00
|
|
|
*/
|
|
|
|
private $repository;
|
|
|
|
|
2020-06-24 04:59:37 +00:00
|
|
|
/**
|
|
|
|
* @var \Mockery\MockInterface
|
|
|
|
*/
|
|
|
|
private $encrypter;
|
|
|
|
|
2017-10-30 02:40:34 +00:00
|
|
|
/**
|
|
|
|
* Setup tests.
|
|
|
|
*/
|
2020-05-09 16:00:52 +00:00
|
|
|
public function setUp(): void
|
2017-10-30 02:40:34 +00:00
|
|
|
{
|
|
|
|
parent::setUp();
|
|
|
|
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->encrypter = m::mock(Encrypter::class);
|
|
|
|
$this->repository = m::mock(NodeRepository::class);
|
2017-10-30 02:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that if we are accessing the daemon.configuration route this middleware is not
|
|
|
|
* applied in order to allow an unauthenticated request to use a token to grab data.
|
|
|
|
*/
|
|
|
|
public function testResponseShouldContinueIfRouteIsExempted()
|
|
|
|
{
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('daemon.configuration');
|
2017-10-30 02:40:34 +00:00
|
|
|
|
|
|
|
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that not passing in the bearer token will result in a HTTP/401 error with the
|
|
|
|
* proper response headers.
|
|
|
|
*/
|
|
|
|
public function testResponseShouldFailIfNoTokenIsProvided()
|
|
|
|
{
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
|
|
|
$this->request->expects('bearerToken')->withNoArgs()->andReturnNull();
|
2017-10-30 02:40:34 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
|
|
|
} catch (HttpException $exception) {
|
|
|
|
$this->assertEquals(401, $exception->getStatusCode(), 'Assert that a status code of 401 is returned.');
|
|
|
|
$this->assertTrue(is_array($exception->getHeaders()), 'Assert that an array of headers is returned.');
|
|
|
|
$this->assertArrayHasKey('WWW-Authenticate', $exception->getHeaders(), 'Assert exception headers contains WWW-Authenticate.');
|
|
|
|
$this->assertEquals('Bearer', $exception->getHeaders()['WWW-Authenticate']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-24 04:59:37 +00:00
|
|
|
* Test that passing in an invalid node daemon secret will result in a bad request
|
|
|
|
* exception being returned.
|
2017-11-05 18:38:39 +00:00
|
|
|
*
|
2020-06-24 04:59:37 +00:00
|
|
|
* @dataProvider badTokenDataProvider
|
2017-10-30 02:40:34 +00:00
|
|
|
*/
|
2020-06-24 04:59:37 +00:00
|
|
|
public function testResponseShouldFailIfTokenFormatIsIncorrect(string $token)
|
2017-10-30 02:40:34 +00:00
|
|
|
{
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->expectException(BadRequestHttpException::class);
|
2017-10-30 02:40:34 +00:00
|
|
|
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
|
|
|
$this->request->expects('bearerToken')->withNoArgs()->andReturn($token);
|
|
|
|
|
|
|
|
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that an access denied error is returned if the node is valid but the token
|
|
|
|
* provided is not valid.
|
|
|
|
*/
|
|
|
|
public function testResponseShouldFailIfTokenIsNotValid()
|
|
|
|
{
|
|
|
|
$this->expectException(AccessDeniedHttpException::class);
|
|
|
|
|
|
|
|
/** @var \Pterodactyl\Models\Node $model */
|
2021-01-23 20:09:16 +00:00
|
|
|
$model = Node::factory()->make();
|
2020-06-24 04:59:37 +00:00
|
|
|
|
|
|
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
|
|
|
$this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.random_string_123');
|
|
|
|
|
|
|
|
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
|
|
|
|
$this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
|
|
|
|
|
|
|
|
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test that an access denied exception is returned if the node is not found using
|
|
|
|
* the token ID provided.
|
|
|
|
*/
|
|
|
|
public function testResponseShouldFailIfNodeIsNotFound()
|
|
|
|
{
|
|
|
|
$this->expectException(AccessDeniedHttpException::class);
|
|
|
|
|
|
|
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
|
|
|
$this->request->expects('bearerToken')->withNoArgs()->andReturn('abcd1234.random_string_123');
|
|
|
|
|
|
|
|
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => 'abcd1234'])->andThrow(RecordNotFoundException::class);
|
2017-10-30 02:40:34 +00:00
|
|
|
|
2017-11-05 18:38:39 +00:00
|
|
|
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
2017-10-30 02:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test a successful middleware process.
|
|
|
|
*/
|
|
|
|
public function testSuccessfulMiddlewareProcess()
|
|
|
|
{
|
2020-06-24 04:59:37 +00:00
|
|
|
/** @var \Pterodactyl\Models\Node $model */
|
2021-01-23 20:09:16 +00:00
|
|
|
$model = Node::factory()->make();
|
2017-10-30 02:40:34 +00:00
|
|
|
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
|
|
|
$this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.' . decrypt($model->daemon_token));
|
2017-10-30 02:40:34 +00:00
|
|
|
|
2020-06-24 04:59:37 +00:00
|
|
|
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
|
|
|
|
$this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
|
2017-10-30 02:40:34 +00:00
|
|
|
|
|
|
|
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
2017-11-02 01:45:43 +00:00
|
|
|
$this->assertRequestHasAttribute('node');
|
|
|
|
$this->assertRequestAttributeEquals($model, 'node');
|
2017-10-30 02:40:34 +00:00
|
|
|
}
|
|
|
|
|
2020-06-24 04:59:37 +00:00
|
|
|
/**
|
|
|
|
* Provides different tokens that should trigger a bad request exception due to
|
|
|
|
* their formatting.
|
|
|
|
*
|
|
|
|
* @return array|\string[][]
|
|
|
|
*/
|
|
|
|
public function badTokenDataProvider(): array
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
['foo'],
|
|
|
|
['foobar'],
|
|
|
|
['foo-bar'],
|
|
|
|
['foo.bar.baz'],
|
|
|
|
['.foo'],
|
|
|
|
['foo.'],
|
|
|
|
['foo..bar'],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2017-10-30 02:40:34 +00:00
|
|
|
/**
|
|
|
|
* Return an instance of the middleware using mocked dependencies.
|
|
|
|
*/
|
|
|
|
private function getMiddleware(): DaemonAuthenticate
|
|
|
|
{
|
2020-06-24 04:59:37 +00:00
|
|
|
return new DaemonAuthenticate($this->encrypter, $this->repository);
|
2017-10-30 02:40:34 +00:00
|
|
|
}
|
|
|
|
}
|