From b47d262ee054f1b4915fdd6b268166f1a2c79912 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 28 Jul 2021 21:23:10 -0700 Subject: [PATCH] Initial pass at deleting as much removed logic as possible; still need to migrate old keys and permissions over --- .../Repository/ApiKeyRepositoryInterface.php | 29 --- .../Api/Client/ApiKeyController.php | 27 --- app/Http/Kernel.php | 20 +-- .../Middleware/Api/AuthenticateIPAccess.php | 38 ---- app/Http/Middleware/Api/AuthenticateKey.php | 95 ---------- app/Http/Middleware/Api/SetSessionDriver.php | 35 ---- .../Api/Client/Account/StoreApiKeyRequest.php | 6 +- app/Models/ApiKey.php | 83 --------- app/Models/User.php | 6 - app/Providers/RepositoryServiceProvider.php | 3 - .../Eloquent/ApiKeyRepository.php | 63 ------- app/Services/Api/KeyCreationService.php | 69 ------- .../Api/Client/ApiKeyTransformer.php | 32 ---- database/Factories/ApiKeyFactory.php | 36 ---- .../Api/AuthenticateIPAccessTest.php | 73 -------- .../Middleware/Api/AuthenticateKeyTest.php | 168 ------------------ .../Middleware/Api/SetSessionDriverTest.php | 44 ----- tests/Unit/Services/Acl/Api/AdminAclTest.php | 46 ----- .../Services/Api/KeyCreationServiceTest.php | 167 ----------------- 19 files changed, 4 insertions(+), 1036 deletions(-) delete mode 100644 app/Contracts/Repository/ApiKeyRepositoryInterface.php delete mode 100644 app/Http/Middleware/Api/AuthenticateIPAccess.php delete mode 100644 app/Http/Middleware/Api/AuthenticateKey.php delete mode 100644 app/Http/Middleware/Api/SetSessionDriver.php delete mode 100644 app/Repositories/Eloquent/ApiKeyRepository.php delete mode 100644 app/Services/Api/KeyCreationService.php delete mode 100644 app/Transformers/Api/Client/ApiKeyTransformer.php delete mode 100644 database/Factories/ApiKeyFactory.php delete mode 100644 tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php delete mode 100644 tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php delete mode 100644 tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php delete mode 100644 tests/Unit/Services/Acl/Api/AdminAclTest.php delete mode 100644 tests/Unit/Services/Api/KeyCreationServiceTest.php diff --git a/app/Contracts/Repository/ApiKeyRepositoryInterface.php b/app/Contracts/Repository/ApiKeyRepositoryInterface.php deleted file mode 100644 index 91e0a3827..000000000 --- a/app/Contracts/Repository/ApiKeyRepositoryInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -encrypter = $encrypter; - $this->repository = $repository; - $this->keyCreationService = $keyCreationService; - } - /** * Returns all of the API keys that exist for the given client. * diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 2d47ec7bf..bd48fa5ec 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http; -use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; use Pterodactyl\Http\Middleware\TrimStrings; @@ -16,21 +15,17 @@ use Pterodactyl\Http\Middleware\AdminAuthenticate; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Pterodactyl\Http\Middleware\Api\AuthenticateKey; use Illuminate\Routing\Middleware\SubstituteBindings; -use Pterodactyl\Http\Middleware\Api\SetSessionDriver; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; use Pterodactyl\Http\Middleware\MaintenanceMiddleware; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; -use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; -use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; @@ -71,24 +66,16 @@ class Kernel extends HttpKernel ], 'api' => [ IsValidJson::class, - ApiSubstituteBindings::class, EnsureFrontendRequestsAreStateful::class, - // SetSessionDriver::class, - // 'api..key:' . ApiKey::TYPE_APPLICATION, + 'auth:sanctum', + ApiSubstituteBindings::class, AuthenticateApplicationUser::class, - // AuthenticateIPAccess::class, ], 'client-api' => [ - // StartSession::class, - // SetSessionDriver::class, - // AuthenticateSession::class, IsValidJson::class, EnsureFrontendRequestsAreStateful::class, 'auth:sanctum', - // 'throttle:api', SubstituteClientApiBindings::class, - // 'api..key:' . ApiKey::TYPE_ACCOUNT, - // AuthenticateIPAccess::class, // This is perhaps a little backwards with the Client API, but logically you'd be unable // to create/get an API key without first enabling 2FA on the account, so I suppose in the // end it makes sense. @@ -118,8 +105,5 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - - // API Specific Middleware - 'api..key' => AuthenticateKey::class, ]; } diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php deleted file mode 100644 index 2af34cfd9..000000000 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ /dev/null @@ -1,38 +0,0 @@ -attributes->get('api_key'); - - if (is_null($model->allowed_ips) || empty($model->allowed_ips)) { - return $next($request); - } - - $find = new IP($request->ip()); - foreach ($model->allowed_ips as $ip) { - if (Range::parse($ip)->contains($find)) { - return $next($request); - } - } - - throw new AccessDeniedHttpException('This IP address (' . $request->ip() . ') does not have permission to access the API using these credentials.'); - } -} diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php deleted file mode 100644 index 1a9ff2e5e..000000000 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ /dev/null @@ -1,95 +0,0 @@ -auth = $auth; - $this->encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Handle an API request by verifying that the provided API key - * is in a valid format and exists in the database. - * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next, int $keyType) - { - if (is_null($request->bearerToken()) && is_null($request->user())) { - throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); - } - - $raw = $request->bearerToken(); - - // This is a request coming through using cookies, we have an authenticated user not using - // an API key. Make some fake API key models and continue on through the process. - if (empty($raw) && $request->user() instanceof User) { - $model = (new ApiKey())->forceFill([ - 'user_id' => $request->user()->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); - } else { - $model = $this->authenticateApiKey($raw, $keyType); - $this->auth->guard()->loginUsingId($model->user_id); - } - - $request->attributes->set('api_key', $model); - - return $next($request); - } - - /** - * Authenticate an API key. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - protected function authenticateApiKey(string $key, int $keyType): ApiKey - { - $identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH); - $token = substr($key, ApiKey::IDENTIFIER_LENGTH); - - try { - $model = $this->repository->findFirstWhere([ - ['identifier', '=', $identifier], - ['key_type', '=', $keyType], - ]); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException(); - } - - if (!hash_equals($this->encrypter->decrypt($model->token), $token)) { - throw new AccessDeniedHttpException(); - } - - $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]); - - return $model; - } -} diff --git a/app/Http/Middleware/Api/SetSessionDriver.php b/app/Http/Middleware/Api/SetSessionDriver.php deleted file mode 100644 index 1c8f59a0b..000000000 --- a/app/Http/Middleware/Api/SetSessionDriver.php +++ /dev/null @@ -1,35 +0,0 @@ -config = $config; - } - - /** - * Set the session for API calls to only last for the one request. - * - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - $this->config->set('session.driver', 'array'); - - return $next($request); - } -} diff --git a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php index d69960c54..f1c926813 100644 --- a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php +++ b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php @@ -2,17 +2,15 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; -use Pterodactyl\Models\ApiKey; +use Pterodactyl\Models\PersonalAccessToken; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class StoreApiKeyRequest extends ClientApiRequest { public function rules(): array { - $rules = ApiKey::getRules(); - return [ - 'description' => $rules['memo'], + 'description' => PersonalAccessToken::getRules()['description'], ]; } } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 40d7ffa83..66f661af7 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -6,38 +6,11 @@ use Pterodactyl\Services\Acl\Api\AdminAcl; class ApiKey extends Model { - /** - * The resource name for this model when it is transformed into an - * API representation using fractal. - */ - public const RESOURCE_NAME = 'api_key'; - /** * Different API keys that can exist on the system. */ - public const TYPE_NONE = 0; public const TYPE_ACCOUNT = 1; public const TYPE_APPLICATION = 2; - public const TYPE_DAEMON_USER = 3; - public const TYPE_DAEMON_APPLICATION = 4; - - /** - * The length of API key identifiers. - */ - public const IDENTIFIER_LENGTH = 16; - - /** - * The length of the actual API key that is encrypted and stored - * in the database. - */ - public const KEY_LENGTH = 32; - - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'api_keys'; /** * Cast values to correct type. @@ -58,60 +31,4 @@ class ApiKey extends Model 'r_' . AdminAcl::RESOURCE_SERVERS => 'int', 'r_' . AdminAcl::RESOURCE_ROLES => 'int', ]; - - /** - * Fields that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'identifier', - 'token', - 'allowed_ips', - 'memo', - 'last_used_at', - ]; - - /** - * Fields that should not be included when calling toArray() or toJson() - * on this model. - * - * @var array - */ - protected $hidden = ['token']; - - /** - * Rules to protect against invalid data entry to DB. - * - * @var array - */ - public static array $validationRules = [ - 'user_id' => 'required|exists:users,id', - 'key_type' => 'present|integer|min:0|max:4', - 'identifier' => 'required|string|size:16|unique:api_keys,identifier', - 'token' => 'required|string', - 'memo' => 'required|nullable|string|max:500', - 'allowed_ips' => 'nullable|array', - 'allowed_ips.*' => 'string', - 'last_used_at' => 'nullable|date', - 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_ROLES => 'integer|min:0|max:3', - ]; - - /** - * @var array - */ - protected $dates = [ - self::CREATED_AT, - self::UPDATED_AT, - 'last_used_at', - ]; } diff --git a/app/Models/User.php b/app/Models/User.php index 52991fb3e..fb6cdb79c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -201,12 +201,6 @@ class User extends Model implements return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); } - public function apiKeys(): HasMany - { - return $this->hasMany(ApiKey::class) - ->where('key_type', ApiKey::TYPE_ACCOUNT); - } - public function servers(): HasMany { return $this->hasMany(Server::class, 'owner_id'); diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 8a0434f52..b91f60ace 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -8,7 +8,6 @@ use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Repositories\Eloquent\UserRepository; -use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\SessionRepository; use Pterodactyl\Repositories\Eloquent\SubuserRepository; @@ -24,7 +23,6 @@ use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; -use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; @@ -47,7 +45,6 @@ class RepositoryServiceProvider extends ServiceProvider { // Eloquent Repositories $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); - $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(EggRepositoryInterface::class, EggRepository::class); diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php deleted file mode 100644 index 67a7e6e6e..000000000 --- a/app/Repositories/Eloquent/ApiKeyRepository.php +++ /dev/null @@ -1,63 +0,0 @@ -getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_ACCOUNT) - ->get($this->getColumns()); - } - - /** - * Get all of the application API keys that exist for a specific user. - */ - public function getApplicationKeys(User $user): Collection - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_APPLICATION) - ->get($this->getColumns()); - } - - /** - * Delete an account API key from the panel for a specific user. - */ - public function deleteAccountKey(User $user, string $identifier): int - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_ACCOUNT) - ->where('identifier', $identifier) - ->delete(); - } - - /** - * Delete an application API key from the panel for a specific user. - */ - public function deleteApplicationKey(User $user, string $identifier): int - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_APPLICATION) - ->where('identifier', $identifier) - ->delete(); - } -} diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php deleted file mode 100644 index 20a11add0..000000000 --- a/app/Services/Api/KeyCreationService.php +++ /dev/null @@ -1,69 +0,0 @@ -encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Set the type of key that should be created. By default an orphaned key will be - * created. These keys cannot be used for anything, and will not render in the UI. - * - * @return \Pterodactyl\Services\Api\KeyCreationService - */ - public function setKeyType(int $type) - { - $this->keyType = $type; - - return $this; - } - - /** - * Create a new API key for the Panel using the permissions passed in the data request. - * This will automatically generate an identifier and an encrypted token that are - * stored in the database. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle(array $data, array $permissions = []): ApiKey - { - $data = array_merge($data, [ - 'key_type' => $this->keyType, - 'identifier' => str_random(ApiKey::IDENTIFIER_LENGTH), - 'token' => $this->encrypter->encrypt(str_random(ApiKey::KEY_LENGTH)), - ]); - - if ($this->keyType === ApiKey::TYPE_APPLICATION) { - $data = array_merge($data, $permissions); - } - - return $this->repository->create($data, true, true); - } -} diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php deleted file mode 100644 index c7c39c2b4..000000000 --- a/app/Transformers/Api/Client/ApiKeyTransformer.php +++ /dev/null @@ -1,32 +0,0 @@ - $model->identifier, - 'description' => $model->memo, - 'allowed_ips' => $model->allowed_ips, - 'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null, - 'created_at' => $model->created_at->toIso8601String(), - ]; - } -} diff --git a/database/Factories/ApiKeyFactory.php b/database/Factories/ApiKeyFactory.php deleted file mode 100644 index 1faa4be55..000000000 --- a/database/Factories/ApiKeyFactory.php +++ /dev/null @@ -1,36 +0,0 @@ - ApiKey::TYPE_APPLICATION, - 'identifier' => Str::random(ApiKey::IDENTIFIER_LENGTH), - 'token' => $token ?: $token = encrypt(Str::random(ApiKey::KEY_LENGTH)), - 'allowed_ips' => null, - 'memo' => 'Test Function Key', - 'created_at' => Carbon::now(), - 'updated_at' => Carbon::now(), - ]; - } -} diff --git a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php deleted file mode 100644 index c362e9cca..000000000 --- a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php +++ /dev/null @@ -1,73 +0,0 @@ -make(['allowed_ips' => []]); - $this->setRequestAttribute('api_key', $model); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test middleware works correctly when a valid IP accesses - * and there is an IP restriction. - */ - public function testWithValidIP() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('127.0.0.1'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that a CIDR range can be used. - */ - public function testValidIPAgainstCIDRRange() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['192.168.1.1/28']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('192.168.1.15'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that an exception is thrown when an invalid IP address - * tries to connect and there is an IP restriction. - */ - public function testWithInvalidIP() - { - $this->expectException(AccessDeniedHttpException::class); - - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->twice()->andReturn('127.0.0.2'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware to be used when testing. - */ - private function getMiddleware(): AuthenticateIPAccess - { - return new AuthenticateIPAccess(); - } -} diff --git a/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php deleted file mode 100644 index 211e810df..000000000 --- a/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php +++ /dev/null @@ -1,168 +0,0 @@ -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('user')->andReturnNull(); - $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull(); - - try { - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - } catch (HttpException $exception) { - $this->assertEquals(401, $exception->getStatusCode()); - $this->assertEquals(['WWW-Authenticate' => 'Bearer'], $exception->getHeaders()); - } - } - - /** - * Test that an invalid API identifier throws an exception. - */ - public function testInvalidIdentifier() - { - $this->expectException(AccessDeniedHttpException::class); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234'); - $this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException()); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - } - - /** - * Test that a valid token can continue past the middleware. - */ - public function testValidToken() - { - $model = ApiKey::factory()->make(); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted'); - $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' => CarbonImmutable::now(), - ])->once()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - $this->assertEquals($model, $this->request->attributes->get('api_key')); - } - - /** - * Test that a valid token can continue past the middleware when set as a user token. - */ - public function testValidTokenWithUserKey() - { - $model = ApiKey::factory()->make(); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted'); - $this->repository->shouldReceive('findFirstWhere')->with([ - ['identifier', '=', $model->identifier], - ['key_type', '=', ApiKey::TYPE_ACCOUNT], - ])->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' => CarbonImmutable::now(), - ])->once()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT); - $this->assertEquals($model, $this->request->attributes->get('api_key')); - } - - /** - * Test that we can still make it though this middleware if the user is logged in and passing - * through a cookie. - */ - public function testAccessWithoutToken() - { - $user = User::factory()->make(['id' => 123]); - - $this->request->shouldReceive('user')->andReturn($user); - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT); - $model = $this->request->attributes->get('api_key'); - - $this->assertSame(ApiKey::TYPE_ACCOUNT, $model->key_type); - $this->assertSame(123, $model->user_id); - $this->assertNull($model->identifier); - } - - /** - * Test that a valid token identifier with an invalid token attached to it - * triggers an exception. - */ - public function testInvalidTokenForIdentifier() - { - $this->expectException(AccessDeniedHttpException::class); - - $model = ApiKey::factory()->make(); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf'); - $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(), ApiKey::TYPE_APPLICATION); - } - - /** - * Return an instance of the middleware with mocked dependencies for testing. - */ - private function getMiddleware(): AuthenticateKey - { - return new AuthenticateKey($this->repository, $this->auth, $this->encrypter); - } -} diff --git a/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php b/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php deleted file mode 100644 index 83fc2b7bc..000000000 --- a/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php +++ /dev/null @@ -1,44 +0,0 @@ -config = m::mock(Repository::class); - } - - /** - * Test that a production environment does not try to disable debug bar. - */ - public function testMiddleware() - { - $this->config->shouldReceive('set')->once()->with('session.driver', 'array')->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware with mocked dependencies for testing. - */ - private function getMiddleware(): SetSessionDriver - { - return new SetSessionDriver($this->config); - } -} diff --git a/tests/Unit/Services/Acl/Api/AdminAclTest.php b/tests/Unit/Services/Acl/Api/AdminAclTest.php deleted file mode 100644 index daaea838f..000000000 --- a/tests/Unit/Services/Acl/Api/AdminAclTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertSame($outcome, AdminAcl::can($permission, $check)); - } - - /** - * Test that checking against a model works as expected. - */ - public function testCheck() - { - $model = ApiKey::factory()->make(['r_servers' => AdminAcl::READ | AdminAcl::WRITE]); - - $this->assertTrue(AdminAcl::check($model, AdminAcl::RESOURCE_SERVERS, AdminAcl::WRITE)); - } - - /** - * Provide valid and invalid permissions combos for testing. - */ - public function permissionsDataProvider(): array - { - return [ - [AdminAcl::READ, AdminAcl::READ, true], - [AdminAcl::READ | AdminAcl::WRITE, AdminAcl::READ, true], - [AdminAcl::READ | AdminAcl::WRITE, AdminAcl::WRITE, true], - [AdminAcl::WRITE, AdminAcl::WRITE, true], - [AdminAcl::READ, AdminAcl::WRITE, false], - [AdminAcl::NONE, AdminAcl::READ, false], - [AdminAcl::NONE, AdminAcl::WRITE, false], - ]; - } -} diff --git a/tests/Unit/Services/Api/KeyCreationServiceTest.php b/tests/Unit/Services/Api/KeyCreationServiceTest.php deleted file mode 100644 index 5c847f144..000000000 --- a/tests/Unit/Services/Api/KeyCreationServiceTest.php +++ /dev/null @@ -1,167 +0,0 @@ -encrypter = m::mock(Encrypter::class); - $this->repository = m::mock(ApiKeyRepositoryInterface::class); - } - - /** - * Test that the service is able to create a keypair and assign the correct permissions. - */ - public function testKeyIsCreated() - { - $model = ApiKey::factory()->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([ - 'test-data' => 'test', - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['test-data' => 'test']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that an identifier is only set by the function. - */ - public function testIdentifierAndTokenAreOnlySetByFunction() - { - $model = ApiKey::factory()->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_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['identifier' => 'customIdentifier', 'token' => 'customToken']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions passed in are loaded onto the key data. - */ - public function testPermissionsAreRetrievedForApplicationKeys() - { - $model = ApiKey::factory()->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 = ApiKey::factory()->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. - */ - 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. - */ - private function getService(): KeyCreationService - { - return new KeyCreationService($this->repository, $this->encrypter); - } -}