Update interface to begin change to seperate account API keys and application keys
Main difference is permissions, cleaner UI for normal users, and account keys use permissions assigned to servers and subusers while application keys use R/W ACLs stored in the key table.
This commit is contained in:
parent
28ebd18f57
commit
f9fc3f4370
18 changed files with 312 additions and 298 deletions
|
@ -3,16 +3,15 @@
|
|||
namespace Tests\Unit\Http\Controllers\Base;
|
||||
|
||||
use Mockery as m;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\ApiKey;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Pterodactyl\Services\Api\KeyCreationService;
|
||||
use Tests\Unit\Http\Controllers\ControllerTestCase;
|
||||
use Pterodactyl\Http\Controllers\Base\APIController;
|
||||
use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest;
|
||||
use Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest;
|
||||
use Pterodactyl\Http\Controllers\Base\AccountKeyController;
|
||||
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
||||
|
||||
class APIControllerTest extends ControllerTestCase
|
||||
class AccountKeyControllerTest extends ControllerTestCase
|
||||
{
|
||||
/**
|
||||
* @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock
|
||||
|
@ -48,7 +47,7 @@ class APIControllerTest extends ControllerTestCase
|
|||
{
|
||||
$model = $this->generateRequestUserModel();
|
||||
|
||||
$this->repository->shouldReceive('findWhere')->with([['user_id', '=', $model->id]])->once()->andReturn(collect(['testkeys']));
|
||||
$this->repository->shouldReceive('getAccountKeys')->with($model)->once()->andReturn(collect(['testkeys']));
|
||||
|
||||
$response = $this->getController()->index($this->request);
|
||||
$this->assertIsViewResponse($response);
|
||||
|
@ -59,51 +58,34 @@ class APIControllerTest extends ControllerTestCase
|
|||
|
||||
/**
|
||||
* Test the create API view controller.
|
||||
*
|
||||
* @dataProvider rootAdminDataProvider
|
||||
*/
|
||||
public function testCreateController($admin)
|
||||
public function testCreateController()
|
||||
{
|
||||
$this->generateRequestUserModel(['root_admin' => $admin]);
|
||||
$this->generateRequestUserModel();
|
||||
|
||||
$response = $this->getController()->create($this->request);
|
||||
$this->assertIsViewResponse($response);
|
||||
$this->assertViewNameEquals('base.api.new', $response);
|
||||
$this->assertViewHasKey('permissions.user', $response);
|
||||
$this->assertViewHasKey('permissions.admin', $response);
|
||||
|
||||
if ($admin) {
|
||||
$this->assertViewKeyNotEquals('permissions.admin', null, $response);
|
||||
} else {
|
||||
$this->assertViewKeyEquals('permissions.admin', null, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the store functionality for a non-admin user.
|
||||
*
|
||||
* @dataProvider rootAdminDataProvider
|
||||
* Test the store functionality for a user.
|
||||
*/
|
||||
public function testStoreController($admin)
|
||||
public function testStoreController()
|
||||
{
|
||||
$this->setRequestMockClass(ApiKeyFormRequest::class);
|
||||
$model = $this->generateRequestUserModel(['root_admin' => $admin]);
|
||||
$this->setRequestMockClass(StoreAccountKeyRequest::class);
|
||||
$model = $this->generateRequestUserModel();
|
||||
$keyModel = factory(ApiKey::class)->make();
|
||||
|
||||
if ($admin) {
|
||||
$this->request->shouldReceive('input')->with('admin_permissions', [])->once()->andReturn(['admin.permission']);
|
||||
}
|
||||
|
||||
$this->request->shouldReceive('user')->withNoArgs()->andReturn($model);
|
||||
$this->request->shouldReceive('input')->with('allowed_ips')->once()->andReturnNull();
|
||||
$this->request->shouldReceive('input')->with('memo')->once()->andReturnNull();
|
||||
$this->request->shouldReceive('input')->with('permissions', [])->once()->andReturn(['test.permission']);
|
||||
|
||||
$this->keyService->shouldReceive('setKeyType')->with(ApiKey::TYPE_ACCOUNT)->once()->andReturnSelf();
|
||||
$this->keyService->shouldReceive('handle')->with([
|
||||
'user_id' => $model->id,
|
||||
'allowed_ips' => null,
|
||||
'memo' => null,
|
||||
], ['test.permission'], ($admin) ? ['admin.permission'] : [])->once()->andReturn($keyModel);
|
||||
])->once()->andReturn($keyModel);
|
||||
|
||||
$this->alert->shouldReceive('success')->with(trans('base.api.index.keypair_created'))->once()->andReturnSelf();
|
||||
$this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull();
|
||||
|
@ -120,34 +102,21 @@ class APIControllerTest extends ControllerTestCase
|
|||
{
|
||||
$model = $this->generateRequestUserModel();
|
||||
|
||||
$this->repository->shouldReceive('deleteWhere')->with([
|
||||
['user_id', '=', $model->id],
|
||||
['token', '=', 'testKey123'],
|
||||
])->once()->andReturn(1);
|
||||
$this->repository->shouldReceive('deleteAccountKey')->with($model, 'testIdentifier')->once()->andReturn(1);
|
||||
|
||||
$response = $this->getController()->revoke($this->request, 'testKey123');
|
||||
$response = $this->getController()->revoke($this->request, 'testIdentifier');
|
||||
$this->assertIsResponse($response);
|
||||
$this->assertEmpty($response->getContent());
|
||||
$this->assertResponseCodeEquals(204, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider to determine if a user is a root admin.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rootAdminDataProvider()
|
||||
{
|
||||
return [[0], [1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the controller with mocked dependencies for testing.
|
||||
*
|
||||
* @return \Pterodactyl\Http\Controllers\Base\APIController
|
||||
* @return \Pterodactyl\Http\Controllers\Base\AccountKeyController
|
||||
*/
|
||||
private function getController(): APIController
|
||||
private function getController(): AccountKeyController
|
||||
{
|
||||
return new APIController($this->alert, $this->repository, $this->keyService);
|
||||
return new AccountKeyController($this->alert, $this->repository, $this->keyService);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Http\Middleware\Api;
|
||||
namespace Tests\Unit\Http\Middleware\Api\Admin;
|
||||
|
||||
use Mockery as m;
|
||||
use Cake\Chronos\Chronos;
|
||||
use Pterodactyl\Models\ApiKey;
|
||||
use Illuminate\Auth\AuthManager;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
|
@ -35,6 +36,7 @@ class AuthenticateKeyTest extends MiddlewareTestCase
|
|||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Chronos::setTestNow(Chronos::now());
|
||||
|
||||
$this->auth = m::mock(AuthManager::class);
|
||||
$this->encrypter = m::mock(Encrypter::class);
|
||||
|
@ -77,10 +79,17 @@ class AuthenticateKeyTest extends MiddlewareTestCase
|
|||
$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->repository->shouldReceive('findFirstWhere')->with([
|
||||
['identifier', '=', $model->identifier],
|
||||
['key_type', '=', ApiKey::TYPE_APPLICATION],
|
||||
])->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->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
|
||||
'last_used_at' => Chronos::now(),
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
$this->assertEquals($model, $this->request->attributes->get('api_key'));
|
||||
}
|
||||
|
@ -96,7 +105,10 @@ class AuthenticateKeyTest extends MiddlewareTestCase
|
|||
$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->repository->shouldReceive('findFirstWhere')->with([
|
||||
['identifier', '=', $model->identifier],
|
||||
['key_type', '=', ApiKey::TYPE_APPLICATION],
|
||||
])->once()->andReturn($model);
|
||||
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
|
|
|
@ -51,6 +51,7 @@ class KeyCreationServiceTest extends TestCase
|
|||
|
||||
$this->repository->shouldReceive('create')->with([
|
||||
'test-data' => 'test',
|
||||
'key_type' => ApiKey::TYPE_NONE,
|
||||
'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH,
|
||||
'token' => $model->token,
|
||||
], true, true)->once()->andReturn($model);
|
||||
|
@ -62,6 +63,9 @@ class KeyCreationServiceTest extends TestCase
|
|||
$this->assertSame($model, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an identifier is only set by the function.
|
||||
*/
|
||||
public function testIdentifierAndTokenAreOnlySetByFunction()
|
||||
{
|
||||
$model = factory(ApiKey::class)->make();
|
||||
|
@ -74,6 +78,7 @@ class KeyCreationServiceTest extends TestCase
|
|||
$this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token);
|
||||
|
||||
$this->repository->shouldReceive('create')->with([
|
||||
'key_type' => ApiKey::TYPE_NONE,
|
||||
'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH,
|
||||
'token' => $model->token,
|
||||
], true, true)->once()->andReturn($model);
|
||||
|
@ -85,6 +90,75 @@ class KeyCreationServiceTest extends TestCase
|
|||
$this->assertSame($model, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that permissions passed in are loaded onto the key data.
|
||||
*/
|
||||
public function testPermissionsAreRetrievedForApplicationKeys()
|
||||
{
|
||||
$model = factory(ApiKey::class)->make();
|
||||
|
||||
$this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random')
|
||||
->expects($this->exactly(2))->willReturnCallback(function ($length) {
|
||||
return 'str_' . $length;
|
||||
});
|
||||
|
||||
$this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token);
|
||||
|
||||
$this->repository->shouldReceive('create')->with([
|
||||
'key_type' => ApiKey::TYPE_APPLICATION,
|
||||
'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH,
|
||||
'token' => $model->token,
|
||||
'permission-key' => 'exists',
|
||||
], true, true)->once()->andReturn($model);
|
||||
|
||||
$response = $this->getService()->setKeyType(ApiKey::TYPE_APPLICATION)->handle([], ['permission-key' => 'exists']);
|
||||
|
||||
$this->assertNotEmpty($response);
|
||||
$this->assertInstanceOf(ApiKey::class, $response);
|
||||
$this->assertSame($model, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that permissions are not retrieved for any key that is not an application key.
|
||||
*
|
||||
* @dataProvider keyTypeDataProvider
|
||||
*/
|
||||
public function testPermissionsAreNotRetrievedForNonApplicationKeys($keyType)
|
||||
{
|
||||
$model = factory(ApiKey::class)->make();
|
||||
|
||||
$this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random')
|
||||
->expects($this->exactly(2))->willReturnCallback(function ($length) {
|
||||
return 'str_' . $length;
|
||||
});
|
||||
|
||||
$this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token);
|
||||
|
||||
$this->repository->shouldReceive('create')->with([
|
||||
'key_type' => $keyType,
|
||||
'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH,
|
||||
'token' => $model->token,
|
||||
], true, true)->once()->andReturn($model);
|
||||
|
||||
$response = $this->getService()->setKeyType($keyType)->handle([], ['fake-permission' => 'should-not-exist']);
|
||||
|
||||
$this->assertNotEmpty($response);
|
||||
$this->assertInstanceOf(ApiKey::class, $response);
|
||||
$this->assertSame($model, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide key types that are not an application specific key.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function keyTypeDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[ApiKey::TYPE_NONE], [ApiKey::TYPE_ACCOUNT], [ApiKey::TYPE_DAEMON_USER], [ApiKey::TYPE_DAEMON_APPLICATION],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the service with mocked dependencies for testing.
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue