Skip to content

Commit dff2e1e

Browse files
committed
Merge branch 'develop' of https://github.com/Pterodactyl/Panel into develop
2 parents 3f6d782 + 285485d commit dff2e1e

File tree

24 files changed

+774
-383
lines changed

24 files changed

+774
-383
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
1919
### Added
2020
* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin.
2121

22+
### Changed
23+
* API keys have been changed to only use a single public key passed in a bearer token. All existing keys can continue being used, however only the first 32 characters should be sent.
24+
2225
## v0.7.0-beta.2 (Derelict Dermodactylus)
2326
### Fixed
2427
* `[beta.1]` — Fixes a CORS header issue due to a wrong API endpoint being provided in the administrative node listing.

app/Contracts/Repository/ApiKeyRepositoryInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99

1010
namespace Pterodactyl\Contracts\Repository;
1111

12+
use Pterodactyl\Models\APIKey;
13+
1214
interface ApiKeyRepositoryInterface extends RepositoryInterface
1315
{
16+
/**
17+
* Load permissions for a key onto the model.
18+
*
19+
* @param \Pterodactyl\Models\APIKey $model
20+
* @param bool $refresh
21+
* @return \Pterodactyl\Models\APIKey
22+
*/
23+
public function loadPermissions(APIKey $model, bool $refresh = false): APIKey;
1424
}

app/Http/Controllers/Base/APIController.php

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,4 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
5-
* Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.com>.
6-
*
7-
* Permission is hereby granted, free of charge, to any person obtaining a copy
8-
* of this software and associated documentation files (the "Software"), to deal
9-
* in the Software without restriction, including without limitation the rights
10-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11-
* copies of the Software, and to permit persons to whom the Software is
12-
* furnished to do so, subject to the following conditions:
13-
*
14-
* The above copyright notice and this permission notice shall be included in all
15-
* copies or substantial portions of the Software.
16-
*
17-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23-
* SOFTWARE.
24-
*/
252

263
namespace Pterodactyl\Http\Controllers\Base;
274

@@ -120,7 +97,7 @@ public function store(ApiKeyFormRequest $request)
12097
'memo' => $request->input('memo'),
12198
], $request->input('permissions', []), $adminPermissions);
12299

123-
$this->alert->success(trans('base.api.index.keypair_created', ['token' => $secret]))->flash();
100+
$this->alert->success(trans('base.api.index.keypair_created'))->flash();
124101

125102
return redirect()->route('account.api');
126103
}
@@ -136,7 +113,7 @@ public function revoke(Request $request, $key)
136113
{
137114
$this->repository->deleteWhere([
138115
['user_id', '=', $request->user()->id],
139-
['public', '=', $key],
116+
['token', '=', $key],
140117
]);
141118

142119
return response('', 204);

app/Http/Kernel.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@
1111
use Pterodactyl\Http\Middleware\VerifyCsrfToken;
1212
use Pterodactyl\Http\Middleware\VerifyReCaptcha;
1313
use Pterodactyl\Http\Middleware\AdminAuthenticate;
14-
use Pterodactyl\Http\Middleware\HMACAuthorization;
1514
use Illuminate\Routing\Middleware\ThrottleRequests;
1615
use Pterodactyl\Http\Middleware\LanguageMiddleware;
1716
use Illuminate\Foundation\Http\Kernel as HttpKernel;
17+
use Pterodactyl\Http\Middleware\API\AuthenticateKey;
1818
use Illuminate\Routing\Middleware\SubstituteBindings;
1919
use Pterodactyl\Http\Middleware\AccessingValidServer;
20+
use Pterodactyl\Http\Middleware\API\SetSessionDriver;
2021
use Illuminate\View\Middleware\ShareErrorsFromSession;
2122
use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
2223
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
24+
use Pterodactyl\Http\Middleware\API\AuthenticateIPAccess;
2325
use Pterodactyl\Http\Middleware\Daemon\DaemonAuthenticate;
2426
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
27+
use Pterodactyl\Http\Middleware\API\HasPermissionToResource;
2528
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
2629
use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer;
2730
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
@@ -42,10 +45,6 @@ class Kernel extends HttpKernel
4245
EncryptCookies::class,
4346
AddQueuedCookiesToResponse::class,
4447
TrimStrings::class,
45-
46-
/*
47-
* Custom middleware applied to all routes.
48-
*/
4948
TrustProxies::class,
5049
];
5150

@@ -66,9 +65,11 @@ class Kernel extends HttpKernel
6665
RequireTwoFactorAuthentication::class,
6766
],
6867
'api' => [
69-
HMACAuthorization::class,
7068
'throttle:60,1',
71-
'bindings',
69+
SubstituteBindings::class,
70+
SetSessionDriver::class,
71+
AuthenticateKey::class,
72+
AuthenticateIPAccess::class,
7273
],
7374
'daemon' => [
7475
SubstituteBindings::class,
@@ -95,6 +96,9 @@ class Kernel extends HttpKernel
9596
'bindings' => SubstituteBindings::class,
9697
'recaptcha' => VerifyReCaptcha::class,
9798

99+
// API specific middleware.
100+
'api..user_level' => HasPermissionToResource::class,
101+
98102
// Server specific middleware (used for authenticating access to resources)
99103
//
100104
// These are only used for individual server authentication, and not gloabl
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware\API;
4+
5+
use Closure;
6+
use IPTools\IP;
7+
use IPTools\Range;
8+
use Illuminate\Http\Request;
9+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
10+
11+
class AuthenticateIPAccess
12+
{
13+
/**
14+
* Determine if a request IP has permission to access the API.
15+
*
16+
* @param \Illuminate\Http\Request $request
17+
* @param \Closure $next
18+
* @return mixed
19+
*
20+
* @throws \Exception
21+
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
22+
*/
23+
public function handle(Request $request, Closure $next)
24+
{
25+
$model = $request->attributes->get('api_key');
26+
27+
if (is_null($model->allowed_ips) || empty($model->allowed_ips)) {
28+
return $next($request);
29+
}
30+
31+
$find = new IP($request->ip());
32+
foreach ($model->allowed_ips as $ip) {
33+
if (Range::parse($ip)->contains($find)) {
34+
return $next($request);
35+
}
36+
}
37+
38+
throw new AccessDeniedHttpException('This IP address does not have permission to access the API using these credentials.');
39+
}
40+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware\API;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Auth\AuthManager;
8+
use Symfony\Component\HttpKernel\Exception\HttpException;
9+
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
10+
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
11+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
12+
13+
class AuthenticateKey
14+
{
15+
/**
16+
* @var \Illuminate\Auth\AuthManager
17+
*/
18+
private $auth;
19+
20+
/**
21+
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
22+
*/
23+
private $repository;
24+
25+
/**
26+
* AuthenticateKey constructor.
27+
*
28+
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
29+
* @param \Illuminate\Auth\AuthManager $auth
30+
*/
31+
public function __construct(
32+
ApiKeyRepositoryInterface $repository,
33+
AuthManager $auth
34+
) {
35+
$this->auth = $auth;
36+
$this->repository = $repository;
37+
}
38+
39+
/**
40+
* Handle an API request by verifying that the provided API key
41+
* is in a valid format, and the route being accessed is allowed
42+
* for the given key.
43+
*
44+
* @param \Illuminate\Http\Request $request
45+
* @param \Closure $next
46+
* @return mixed
47+
*
48+
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
49+
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
50+
*/
51+
public function handle(Request $request, Closure $next)
52+
{
53+
if (is_null($request->bearerToken())) {
54+
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
55+
}
56+
57+
try {
58+
$model = $this->repository->findFirstWhere([['token', '=', $request->bearerToken()]]);
59+
} catch (RecordNotFoundException $exception) {
60+
throw new AccessDeniedHttpException;
61+
}
62+
63+
$this->auth->guard()->loginUsingId($model->user_id);
64+
$request->attributes->set('api_key', $model);
65+
66+
return $next($request);
67+
}
68+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware\API;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
8+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
9+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
10+
11+
class HasPermissionToResource
12+
{
13+
/**
14+
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
15+
*/
16+
private $repository;
17+
18+
/**
19+
* HasPermissionToResource constructor.
20+
*
21+
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
22+
*/
23+
public function __construct(ApiKeyRepositoryInterface $repository)
24+
{
25+
$this->repository = $repository;
26+
}
27+
28+
/**
29+
* Determine if an API key has permission to access the given route.
30+
*
31+
* @param \Illuminate\Http\Request $request
32+
* @param \Closure $next
33+
* @param string $role
34+
* @return mixed
35+
*/
36+
public function handle(Request $request, Closure $next, string $role = 'admin')
37+
{
38+
/** @var \Pterodactyl\Models\APIKey $model */
39+
$model = $request->attributes->get('api_key');
40+
41+
if ($role === 'admin' && ! $request->user()->root_admin) {
42+
throw new NotFoundHttpException;
43+
}
44+
45+
$this->repository->loadPermissions($model);
46+
$routeKey = str_replace(['api.', 'admin.'], '', $request->route()->getName());
47+
48+
$count = $model->getRelation('permissions')->filter(function ($permission) use ($routeKey) {
49+
return $routeKey === str_replace('-', '.', $permission->permission);
50+
})->count();
51+
52+
if ($count === 1) {
53+
return $next($request);
54+
}
55+
56+
throw new AccessDeniedHttpException('Cannot access resource without required `' . $routeKey . '` permission.');
57+
}
58+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware\API;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Barryvdh\Debugbar\LaravelDebugbar;
8+
use Illuminate\Contracts\Foundation\Application;
9+
use Illuminate\Contracts\Config\Repository as ConfigRepository;
10+
11+
class SetSessionDriver
12+
{
13+
/**
14+
* @var \Illuminate\Contracts\Foundation\Application
15+
*/
16+
private $app;
17+
18+
/**
19+
* @var \Illuminate\Contracts\Config\Repository
20+
*/
21+
private $config;
22+
23+
/**
24+
* SetSessionDriver constructor.
25+
*
26+
* @param \Illuminate\Contracts\Foundation\Application $app
27+
* @param \Illuminate\Contracts\Config\Repository $config
28+
*/
29+
public function __construct(Application $app, ConfigRepository $config)
30+
{
31+
$this->app = $app;
32+
$this->config = $config;
33+
}
34+
35+
/**
36+
* Set the session for API calls to only last for the one request.
37+
*
38+
* @param \Illuminate\Http\Request $request
39+
* @param \Closure $next
40+
* @return mixed
41+
*/
42+
public function handle(Request $request, Closure $next)
43+
{
44+
if ($this->app->environment() !== 'production') {
45+
$this->app->make(LaravelDebugbar::class)->disable();
46+
}
47+
48+
$this->config->set('session.driver', 'array');
49+
50+
return $next($request);
51+
}
52+
}

0 commit comments

Comments
 (0)