misc_pterodactyl-panel/tests/Unit/Http/Middleware/Api/Admin/AuthenticateKeyTest.php
Dane Everitt e3df0738da
Change the way API keys are stored and validated; clarify API namespacing
Previously, a single key was used to access the API, this has not changed in terms of what the user sees. However, API keys now use an identifier and token internally. The identifier is the first 16 characters of the key, and the token is the remaining 32. The token is stored encrypted at rest in the database and the identifier is used by the API middleware to grab that record and make a timing attack safe comparison.
2018-01-13 16:06:19 -06:00

114 lines
4.1 KiB
PHP

<?php
namespace Tests\Unit\Http\Middleware\Api;
use Mockery as m;
use Pterodactyl\Models\APIKey;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Encryption\Encrypter;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Http\Middleware\Api\Admin\AuthenticateKey;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
class AuthenticateKeyTest extends MiddlewareTestCase
{
/**
* @var \Illuminate\Auth\AuthManager|\Mockery\Mock
*/
private $auth;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->auth = m::mock(AuthManager::class);
$this->encrypter = m::mock(Encrypter::class);
$this->repository = m::mock(ApiKeyRepositoryInterface::class);
}
/**
* Test that a missing bearer token will throw an exception.
*/
public function testMissingBearerTokenThrowsException()
{
$this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull();
try {
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} catch (HttpException $exception) {
$this->assertEquals(401, $exception->getStatusCode());
$this->assertEquals(['WWW-Authenticate' => 'Bearer'], $exception->getHeaders());
}
}
/**
* Test that an invalid API identifer throws an exception.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function testInvalidIdentifier()
{
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234');
$this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
}
/**
* Test that a valid token can continue past the middleware.
*/
public function testValidToken()
{
$model = factory(APIKey::class)->make();
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted');
$this->repository->shouldReceive('findFirstWhere')->with([['identifier', '=', $model->identifier]])->once()->andReturn($model);
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
$this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->assertEquals($model, $this->request->attributes->get('api_key'));
}
/**
* Test that a valid token identifier with an invalid token attached to it
* triggers an exception.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function testInvalidTokenForIdentifier()
{
$model = factory(APIKey::class)->make();
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf');
$this->repository->shouldReceive('findFirstWhere')->with([['identifier', '=', $model->identifier]])->once()->andReturn($model);
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
}
/**
* Return an instance of the middleware with mocked dependencies for testing.
*
* @return \Pterodactyl\Http\Middleware\Api\Admin\AuthenticateKey
*/
private function getMiddleware(): AuthenticateKey
{
return new AuthenticateKey($this->repository, $this->auth, $this->encrypter);
}
}