Merge branch 'release/v0.7.5'

This commit is contained in:
Dane Everitt 2018-03-03 22:31:37 -06:00
commit a3b47e3568
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
27 changed files with 224 additions and 126 deletions

View file

@ -3,6 +3,20 @@ This file is a running track of new features and fixes to each version of the pa
This project follows [Semantic Versioning](http://semver.org) guidelines. This project follows [Semantic Versioning](http://semver.org) guidelines.
## v0.7.5 (Derelict Dermodactylus)
### Fixed
* Fixes application API keys being created as a client API key.
* Search term is now passed through when using paginated result sets.
* Reduces the number of SQL queries executed when rendering the server listing to increase performance.
* Fixes exceptions being thrown for non-existent subuser permissions.
* Fixes exception caused when trying to revoke admin privileges from a user account due to a bad endpoint.
### Changed
* Databases are now properly paginated when viewing a database host.
* No more loading daemon keys for every server model being loaded, some of us value our databases.
* Changed behavior of the subuser middleware to add a daemon access key if one is missing from the database for some reason.
* Server short-codes are now based on the UUID as they were in previous versions of Pterodactyl.
## v0.7.4-h1 (Derelict Dermodactylus) ## v0.7.4-h1 (Derelict Dermodactylus)
### Fixed ### Fixed
* Being able to create servers is kind of a core aspect of the software, pushing releases late at night is not a great idea. * Being able to create servers is kind of a core aspect of the software, pushing releases late at night is not a great idea.

View file

@ -22,9 +22,9 @@ Some of our core supported games include:
* Teamspeak * Teamspeak
* Mumble * Mumble
* Team Fortress 2 * Team Fortress 2
* Counter Strike: GO * Counter Strike: Global Offensive
* Garrys Mod * Garry's Mod
* Ark: Survival Evolved * ARK: Survival Evolved
In addition to our standard nest of supported games, our community is constantly pushing the limits of this software and there are plenty more games available provided by the community. Some of these games include: In addition to our standard nest of supported games, our community is constantly pushing the limits of this software and there are plenty more games available provided by the community. Some of these games include:

View file

@ -3,7 +3,6 @@
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Pterodactyl\Models\DatabaseHost;
interface DatabaseHostRepositoryInterface extends RepositoryInterface interface DatabaseHostRepositoryInterface extends RepositoryInterface
{ {
@ -14,15 +13,4 @@ interface DatabaseHostRepositoryInterface extends RepositoryInterface
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function getWithViewDetails(): Collection; public function getWithViewDetails(): Collection;
/**
* Return a database host with the databases and associated servers
* that are attached to said databases.
*
* @param int $id
* @return \Pterodactyl\Models\DatabaseHost
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getWithServers(int $id): DatabaseHost;
} }

View file

@ -1,16 +1,10 @@
<?php <?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Pterodactyl\Models\Database; use Pterodactyl\Models\Database;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
interface DatabaseRepositoryInterface extends RepositoryInterface interface DatabaseRepositoryInterface extends RepositoryInterface
{ {
@ -39,6 +33,15 @@ interface DatabaseRepositoryInterface extends RepositoryInterface
*/ */
public function getDatabasesForServer(int $server): Collection; public function getDatabasesForServer(int $server): Collection;
/**
* Return all of the databases for a given host with the server relationship loaded.
*
* @param int $host
* @param int $count
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function getDatabasesForHost(int $host, int $count = 25): LengthAwarePaginator;
/** /**
* Create a new database if it does not already exist on the host with * Create a new database if it does not already exist on the host with
* the provided details. * the provided details.

View file

@ -136,4 +136,13 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
* @return int * @return int
*/ */
public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int; public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int;
/**
* Check if a given UUID and UUID-Short string are unique to a server.
*
* @param string $uuid
* @param string $short
* @return bool
*/
public function isUniqueUuidCombo(string $uuid, string $short): bool;
} }

View file

@ -1,11 +1,4 @@
<?php <?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
@ -19,6 +12,7 @@ use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest; use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest;
use Pterodactyl\Services\Databases\Hosts\HostCreationService; use Pterodactyl\Services\Databases\Hosts\HostCreationService;
use Pterodactyl\Services\Databases\Hosts\HostDeletionService; use Pterodactyl\Services\Databases\Hosts\HostDeletionService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
@ -34,6 +28,11 @@ class DatabaseController extends Controller
*/ */
private $creationService; private $creationService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
private $databaseRepository;
/** /**
* @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService
*/ */
@ -59,6 +58,7 @@ class DatabaseController extends Controller
* *
* @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository
* @param \Pterodactyl\Services\Databases\Hosts\HostCreationService $creationService * @param \Pterodactyl\Services\Databases\Hosts\HostCreationService $creationService
* @param \Pterodactyl\Services\Databases\Hosts\HostDeletionService $deletionService * @param \Pterodactyl\Services\Databases\Hosts\HostDeletionService $deletionService
* @param \Pterodactyl\Services\Databases\Hosts\HostUpdateService $updateService * @param \Pterodactyl\Services\Databases\Hosts\HostUpdateService $updateService
@ -67,6 +67,7 @@ class DatabaseController extends Controller
public function __construct( public function __construct(
AlertsMessageBag $alert, AlertsMessageBag $alert,
DatabaseHostRepositoryInterface $repository, DatabaseHostRepositoryInterface $repository,
DatabaseRepositoryInterface $databaseRepository,
HostCreationService $creationService, HostCreationService $creationService,
HostDeletionService $deletionService, HostDeletionService $deletionService,
HostUpdateService $updateService, HostUpdateService $updateService,
@ -74,6 +75,7 @@ class DatabaseController extends Controller
) { ) {
$this->alert = $alert; $this->alert = $alert;
$this->creationService = $creationService; $this->creationService = $creationService;
$this->databaseRepository = $databaseRepository;
$this->deletionService = $deletionService; $this->deletionService = $deletionService;
$this->repository = $repository; $this->repository = $repository;
$this->locationRepository = $locationRepository; $this->locationRepository = $locationRepository;
@ -105,7 +107,8 @@ class DatabaseController extends Controller
{ {
return view('admin.databases.view', [ return view('admin.databases.view', [
'locations' => $this->locationRepository->getAllWithNodes(), 'locations' => $this->locationRepository->getAllWithNodes(),
'host' => $this->repository->getWithServers($host), 'host' => $this->repository->find($host),
'databases' => $this->databaseRepository->getDatabasesForHost($host),
]); ]);
} }

View file

@ -11,7 +11,6 @@ namespace Pterodactyl\Http\Middleware\Server;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Contracts\Session\Session;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@ -23,21 +22,14 @@ class AuthenticateAsSubuser
*/ */
private $keyProviderService; private $keyProviderService;
/**
* @var \Illuminate\Contracts\Session\Session
*/
private $session;
/** /**
* SubuserAccessAuthenticate constructor. * SubuserAccessAuthenticate constructor.
* *
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
* @param \Illuminate\Contracts\Session\Session $session
*/ */
public function __construct(DaemonKeyProviderService $keyProviderService, Session $session) public function __construct(DaemonKeyProviderService $keyProviderService)
{ {
$this->keyProviderService = $keyProviderService; $this->keyProviderService = $keyProviderService;
$this->session = $session;
} }
/** /**
@ -60,7 +52,6 @@ class AuthenticateAsSubuser
throw new AccessDeniedHttpException('This account does not have permission to access this server.'); throw new AccessDeniedHttpException('This account does not have permission to access this server.');
} }
$this->session->now('server_data.token', $token);
$request->attributes->set('server_token', $token); $request->attributes->set('server_token', $token);
return $next($request); return $next($request);

View file

@ -35,13 +35,6 @@ class Server extends Model implements CleansAttributes, ValidableContract
*/ */
protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at'];
/**
* Always eager load these relationships on the model.
*
* @var array
*/
protected $with = ['key'];
/** /**
* Fields that are not mass assignable. * Fields that are not mass assignable.
* *

View file

@ -115,8 +115,8 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa
public function revokeAccessKey($key): ResponseInterface public function revokeAccessKey($key): ResponseInterface
{ {
if (is_array($key)) { if (is_array($key)) {
return $this->getHttpClient()->request('POST', 'keys', [ return $this->getHttpClient()->request('POST', 'keys/batch-delete', [
'json' => $key, 'json' => ['keys' => $key],
]); ]);
} }

View file

@ -4,8 +4,6 @@ namespace Pterodactyl\Repositories\Eloquent;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Models\DatabaseHost;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
class DatabaseHostRepository extends EloquentRepository implements DatabaseHostRepositoryInterface class DatabaseHostRepository extends EloquentRepository implements DatabaseHostRepositoryInterface
@ -30,22 +28,4 @@ class DatabaseHostRepository extends EloquentRepository implements DatabaseHostR
{ {
return $this->getBuilder()->withCount('databases')->with('node')->get(); return $this->getBuilder()->withCount('databases')->with('node')->get();
} }
/**
* Return a database host with the databases and associated servers
* that are attached to said databases.
*
* @param int $id
* @return \Pterodactyl\Models\DatabaseHost
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getWithServers(int $id): DatabaseHost
{
try {
return $this->getBuilder()->with('databases.server')->findOrFail($id, $this->getColumns());
} catch (ModelNotFoundException $exception) {
throw new RecordNotFoundException;
}
}
} }

View file

@ -6,6 +6,7 @@ use Pterodactyl\Models\Database;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Database\DatabaseManager; use Illuminate\Database\DatabaseManager;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException; use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException;
@ -78,6 +79,20 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor
return $this->getBuilder()->where('server_id', $server)->get($this->getColumns()); return $this->getBuilder()->where('server_id', $server)->get($this->getColumns());
} }
/**
* Return all of the databases for a given host with the server relationship loaded.
*
* @param int $host
* @param int $count
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function getDatabasesForHost(int $host, int $count = 25): LengthAwarePaginator
{
return $this->getBuilder()->with('server')
->where('database_host_id', $host)
->paginate($count, $this->getColumns());
}
/** /**
* Create a new database if it does not already exist on the host with * Create a new database if it does not already exist on the host with
* the provided details. * the provided details.

View file

@ -216,7 +216,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
*/ */
public function filterUserAccessServers(User $user, int $level, bool $paginate = true) public function filterUserAccessServers(User $user, int $level, bool $paginate = true)
{ {
$instance = $this->getBuilder()->select($this->getColumns())->with(['user']); $instance = $this->getBuilder()->select($this->getColumns())->with(['user', 'node', 'allocation']);
// If access level is set to owner, only display servers // If access level is set to owner, only display servers
// that the user owns. // that the user owns.
@ -303,6 +303,18 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
return $this->getServersForPowerAction($servers, $nodes, true); return $this->getServersForPowerAction($servers, $nodes, true);
} }
/**
* Check if a given UUID and UUID-Short string are unique to a server.
*
* @param string $uuid
* @param string $short
* @return bool
*/
public function isUniqueUuidCombo(string $uuid, string $short): bool
{
return ! $this->getBuilder()->where('uuid', '=', $uuid)->orWhere('uuidShort', '=', $short)->exists();
}
/** /**
* Return an array of server IDs that a given user can access based * Return an array of server IDs that a given user can access based
* on owner and subuser permissions. * on owner and subuser permissions.

View file

@ -28,6 +28,7 @@ use Carbon\Carbon;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
class DaemonKeyProviderService class DaemonKeyProviderService
@ -47,21 +48,29 @@ class DaemonKeyProviderService
*/ */
private $repository; private $repository;
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
*/
private $subuserRepository;
/** /**
* GetDaemonKeyService constructor. * GetDaemonKeyService constructor.
* *
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService
* @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository
*/ */
public function __construct( public function __construct(
DaemonKeyCreationService $keyCreationService, DaemonKeyCreationService $keyCreationService,
DaemonKeyRepositoryInterface $repository, DaemonKeyRepositoryInterface $repository,
DaemonKeyUpdateService $keyUpdateService DaemonKeyUpdateService $keyUpdateService,
SubuserRepositoryInterface $subuserRepository
) { ) {
$this->keyCreationService = $keyCreationService; $this->keyCreationService = $keyCreationService;
$this->keyUpdateService = $keyUpdateService; $this->keyUpdateService = $keyUpdateService;
$this->repository = $repository; $this->repository = $repository;
$this->subuserRepository = $subuserRepository;
} }
/** /**
@ -89,10 +98,18 @@ class DaemonKeyProviderService
return $this->keyCreationService->handle($server->id, $user->id); return $this->keyCreationService->handle($server->id, $user->id);
} }
// If they aren't the admin or owner of the server, they shouldn't get access. // Check if user is a subuser for this server. Ideally they should always have
// Subusers should always have an entry created when they are, so if there is // a record associated with them in the database, but we should still handle
// no record, it should fail. // that potentiality here.
throw $exception; //
// If no subuser is found, a RecordNotFoundException will be thrown, thus handling
// the parent error as well.
$subuser = $this->subuserRepository->findFirstWhere([
['user_id', '=', $user->id],
['server_id', '=', $server->id],
]);
return $this->keyCreationService->handle($subuser->server_id, $subuser->user_id);
} }
if (! $updateIfExpired || Carbon::now()->diffInSeconds($key->expires_at, false) > 0) { if (! $updateIfExpired || Carbon::now()->diffInSeconds($key->expires_at, false) > 0) {

View file

@ -210,10 +210,12 @@ class ServerCreationService
*/ */
private function createModel(array $data): Server private function createModel(array $data): Server
{ {
$uuid = $this->generateUniqueUuidCombo();
return $this->repository->create([ return $this->repository->create([
'external_id' => array_get($data, 'external_id'), 'external_id' => array_get($data, 'external_id'),
'uuid' => Uuid::uuid4()->toString(), 'uuid' => $uuid,
'uuidShort' => str_random(8), 'uuidShort' => substr($uuid, 0, 8),
'node_id' => array_get($data, 'node_id'), 'node_id' => array_get($data, 'node_id'),
'name' => array_get($data, 'name'), 'name' => array_get($data, 'name'),
'description' => array_get($data, 'description') ?? '', 'description' => array_get($data, 'description') ?? '',
@ -289,4 +291,20 @@ class ServerCreationService
return $allocation->node_id; return $allocation->node_id;
} }
/**
* Create a unique UUID and UUID-Short combo for a server.
*
* @return string
*/
private function generateUniqueUuidCombo(): string
{
$uuid = Uuid::uuid4()->toString();
if (! $this->repository->isUniqueUuidCombo($uuid, substr($uuid, 0, 8))) {
return $this->generateUniqueUuidCombo();
}
return $uuid;
}
} }

View file

@ -1,26 +1,4 @@
<?php <?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Transformers\Daemon; namespace Pterodactyl\Transformers\Daemon;
@ -83,8 +61,8 @@ class ApiKeyTransformer extends TransformerAbstract
$daemonPermissions = ['s:console']; $daemonPermissions = ['s:console'];
foreach ($permissions as $permission) { foreach ($permissions as $permission) {
if (! is_null($mappings[$permission])) { if (! is_null(array_get($mappings, $permission))) {
$daemonPermissions[] = $mappings[$permission]; $daemonPermissions[] = array_get($mappings, $permission);
} }
} }

View file

@ -9,7 +9,7 @@ return [
| change this value if you are not maintaining your own internal versions. | change this value if you are not maintaining your own internal versions.
*/ */
'version' => '0.7.4.1', 'version' => '0.7.5',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -15,7 +15,7 @@
@section('content') @section('content')
<div class="row"> <div class="row">
<form method="POST" action="{{ route('account.api.new') }}"> <form method="POST" action="{{ route('admin.api.new') }}">
<div class="col-sm-8 col-xs-12"> <div class="col-sm-8 col-xs-12">
<div class="box box-primary"> <div class="box box-primary">
<div class="box-header with-border"> <div class="box-header with-border">

View file

@ -101,14 +101,14 @@
<th>Connections From</th> <th>Connections From</th>
<th></th> <th></th>
</tr> </tr>
@foreach($host->databases as $database) @foreach($databases as $database)
<tr> <tr>
<td class="middle"><a href="{{ route('admin.servers.view', $database->server->id) }}">{{ $database->server->name }}</a></td> <td class="middle"><a href="{{ route('admin.servers.view', $database->getRelation('server')->id) }}">{{ $database->getRelation('server')->name }}</a></td>
<td class="middle">{{ $database->database }}</td> <td class="middle">{{ $database->database }}</td>
<td class="middle">{{ $database->username }}</td> <td class="middle">{{ $database->username }}</td>
<td class="middle">{{ $database->remote }}</td> <td class="middle">{{ $database->remote }}</td>
<td class="text-center"> <td class="text-center">
<a href="{{ route('admin.servers.view.database', $database->server->id) }}"> <a href="{{ route('admin.servers.view.database', $database->getRelation('server')->id) }}">
<button class="btn btn-xs btn-primary">Manage</button> <button class="btn btn-xs btn-primary">Manage</button>
</a> </a>
</td> </td>
@ -116,6 +116,11 @@
@endforeach @endforeach
</table> </table>
</div> </div>
@if($databases->hasPages())
<div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $databases->render() !!}</div>
</div>
@endif
</div> </div>
</div> </div>
</div> </div>

View file

@ -70,7 +70,7 @@
</div> </div>
@if($nodes->hasPages()) @if($nodes->hasPages())
<div class="box-footer with-border"> <div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $nodes->render() !!}</div> <div class="col-md-12 text-center">{!! $nodes->appends(['query' => Request::input('query')])->render() !!}</div>
</div> </div>
@endif @endif
</div> </div>

View file

@ -61,7 +61,7 @@
</div> </div>
@if ($packs->hasPages()) @if ($packs->hasPages())
<div class="box-footer with-border"> <div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $packs->render() !!}</div> <div class="col-md-12 text-center">{!! $packs->appends(['query' => Request::input('query')])->render() !!}</div>
</div> </div>
@endif @endif
</div> </div>

View file

@ -76,7 +76,7 @@
</div> </div>
@if($servers->hasPages()) @if($servers->hasPages())
<div class="box-footer with-border"> <div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $servers->render() !!}</div> <div class="col-md-12 text-center">{!! $servers->appends(['query' => Request::input('query')])->render() !!}</div>
</div> </div>
@endif @endif
</div> </div>

View file

@ -75,7 +75,7 @@
</div> </div>
@if($users->hasPages()) @if($users->hasPages())
<div class="box-footer with-border"> <div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $users->render() !!}</div> <div class="col-md-12 text-center">{!! $users->appends(['query' => Request::input('query')])->render() !!}</div>
</div> </div>
@endif @endif
</div> </div>

View file

@ -51,8 +51,8 @@
<tr class="dynamic-update" data-server="{{ $server->uuidShort }}"> <tr class="dynamic-update" data-server="{{ $server->uuidShort }}">
<td @if(! empty($server->description)) rowspan="2" @endif><code>{{ $server->uuidShort }}</code></td> <td @if(! empty($server->description)) rowspan="2" @endif><code>{{ $server->uuidShort }}</code></td>
<td><a href="{{ route('server.index', $server->uuidShort) }}">{{ $server->name }}</a></td> <td><a href="{{ route('server.index', $server->uuidShort) }}">{{ $server->name }}</a></td>
<td>{{ $server->node->name }}</td> <td>{{ $server->getRelation('node')->name }}</td>
<td><code>{{ $server->allocation->alias }}:{{ $server->allocation->port }}</code></td> <td><code>{{ $server->getRelation('allocation')->alias }}:{{ $server->getRelation('allocation')->port }}</code></td>
<td class="text-center hidden-sm hidden-xs"><span data-action="memory">--</span> / {{ $server->memory === 0 ? '&infin;' : $server->memory }} MB</td> <td class="text-center hidden-sm hidden-xs"><span data-action="memory">--</span> / {{ $server->memory === 0 ? '&infin;' : $server->memory }} MB</td>
<td class="text-center hidden-sm hidden-xs"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">--</span> %</td> <td class="text-center hidden-sm hidden-xs"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">--</span> %</td>
<td class="text-center"> <td class="text-center">
@ -79,7 +79,7 @@
</div> </div>
@if($servers->hasPages()) @if($servers->hasPages())
<div class="box-footer"> <div class="box-footer">
<div class="col-md-12 text-center">{!! $servers->render() !!}</div> <div class="col-md-12 text-center">{!! $servers->appends(['query' => Request::input('query')])->render() !!}</div>
</div> </div>
@endif @endif
</div> </div>

View file

@ -13,11 +13,13 @@ use Mockery as m;
use Tests\TestCase; use Tests\TestCase;
use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Models\DatabaseHost;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Pagination\LengthAwarePaginator;
use Tests\Assertions\ControllerAssertionsTrait; use Tests\Assertions\ControllerAssertionsTrait;
use Pterodactyl\Http\Controllers\Admin\DatabaseController; use Pterodactyl\Http\Controllers\Admin\DatabaseController;
use Pterodactyl\Services\Databases\Hosts\HostUpdateService; use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
use Pterodactyl\Services\Databases\Hosts\HostCreationService; use Pterodactyl\Services\Databases\Hosts\HostCreationService;
use Pterodactyl\Services\Databases\Hosts\HostDeletionService; use Pterodactyl\Services\Databases\Hosts\HostDeletionService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
@ -35,6 +37,11 @@ class DatabaseControllerTest extends TestCase
*/ */
private $creationService; private $creationService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface|\Mockery\Mock
*/
private $databaseRepository;
/** /**
* @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService|\Mockery\Mock * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService|\Mockery\Mock
*/ */
@ -64,6 +71,7 @@ class DatabaseControllerTest extends TestCase
$this->alert = m::mock(AlertsMessageBag::class); $this->alert = m::mock(AlertsMessageBag::class);
$this->creationService = m::mock(HostCreationService::class); $this->creationService = m::mock(HostCreationService::class);
$this->databaseRepository = m::mock(DatabaseRepositoryInterface::class);
$this->deletionService = m::mock(HostDeletionService::class); $this->deletionService = m::mock(HostDeletionService::class);
$this->locationRepository = m::mock(LocationRepositoryInterface::class); $this->locationRepository = m::mock(LocationRepositoryInterface::class);
$this->repository = m::mock(DatabaseHostRepositoryInterface::class); $this->repository = m::mock(DatabaseHostRepositoryInterface::class);
@ -94,9 +102,14 @@ class DatabaseControllerTest extends TestCase
public function testViewController() public function testViewController()
{ {
$model = factory(DatabaseHost::class)->make(); $model = factory(DatabaseHost::class)->make();
$paginator = new LengthAwarePaginator([], 1, 1);
$this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn(collect(['getAllWithNodes'])); $this->locationRepository->shouldReceive('getAllWithNodes')->withNoArgs()->once()->andReturn(collect(['getAllWithNodes']));
$this->repository->shouldReceive('getWithServers')->with(1)->once()->andReturn($model); $this->repository->shouldReceive('find')->with(1)->once()->andReturn($model);
$this->databaseRepository->shouldReceive('getDatabasesForHost')
->once()
->with(1)
->andReturn($paginator);
$response = $this->getController()->view(1); $response = $this->getController()->view(1);
@ -104,8 +117,10 @@ class DatabaseControllerTest extends TestCase
$this->assertViewNameEquals('admin.databases.view', $response); $this->assertViewNameEquals('admin.databases.view', $response);
$this->assertViewHasKey('locations', $response); $this->assertViewHasKey('locations', $response);
$this->assertViewHasKey('host', $response); $this->assertViewHasKey('host', $response);
$this->assertViewHasKey('databases', $response);
$this->assertViewKeyEquals('locations', collect(['getAllWithNodes']), $response); $this->assertViewKeyEquals('locations', collect(['getAllWithNodes']), $response);
$this->assertViewKeyEquals('host', $model, $response); $this->assertViewKeyEquals('host', $model, $response);
$this->assertViewKeyEquals('databases', $paginator, $response);
} }
/** /**
@ -118,6 +133,7 @@ class DatabaseControllerTest extends TestCase
return new DatabaseController( return new DatabaseController(
$this->alert, $this->alert,
$this->repository, $this->repository,
$this->databaseRepository,
$this->creationService, $this->creationService,
$this->deletionService, $this->deletionService,
$this->updateService, $this->updateService,

View file

@ -4,7 +4,6 @@ namespace Tests\Unit\Http\Middleware\Server;
use Mockery as m; use Mockery as m;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Illuminate\Contracts\Session\Session;
use Tests\Unit\Http\Middleware\MiddlewareTestCase; use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
@ -17,11 +16,6 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
*/ */
private $keyProviderService; private $keyProviderService;
/**
* @var \Illuminate\Contracts\Session\Session|\Mockery\Mock
*/
private $session;
/** /**
* Setup tests. * Setup tests.
*/ */
@ -30,7 +24,6 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
parent::setUp(); parent::setUp();
$this->keyProviderService = m::mock(DaemonKeyProviderService::class); $this->keyProviderService = m::mock(DaemonKeyProviderService::class);
$this->session = m::mock(Session::class);
} }
/** /**
@ -43,7 +36,6 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
$this->setRequestAttribute('server', $model); $this->setRequestAttribute('server', $model);
$this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andReturn('abc123'); $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andReturn('abc123');
$this->session->shouldReceive('now')->with('server_data.token', 'abc123')->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->assertRequestHasAttribute('server_token'); $this->assertRequestHasAttribute('server_token');
@ -74,6 +66,6 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
*/ */
public function getMiddleware(): AuthenticateAsSubuser public function getMiddleware(): AuthenticateAsSubuser
{ {
return new AuthenticateAsSubuser($this->keyProviderService, $this->session); return new AuthenticateAsSubuser($this->keyProviderService);
} }
} }

View file

@ -14,11 +14,13 @@ use Carbon\Carbon;
use Tests\TestCase; use Tests\TestCase;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Models\DaemonKey; use Pterodactyl\Models\DaemonKey;
use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService; use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService;
use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
class DaemonKeyProviderServiceTest extends TestCase class DaemonKeyProviderServiceTest extends TestCase
@ -38,6 +40,11 @@ class DaemonKeyProviderServiceTest extends TestCase
*/ */
private $repository; private $repository;
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock
*/
private $subuserRepository;
/** /**
* Setup tests. * Setup tests.
*/ */
@ -49,6 +56,7 @@ class DaemonKeyProviderServiceTest extends TestCase
$this->keyCreationService = m::mock(DaemonKeyCreationService::class); $this->keyCreationService = m::mock(DaemonKeyCreationService::class);
$this->keyUpdateService = m::mock(DaemonKeyUpdateService::class); $this->keyUpdateService = m::mock(DaemonKeyUpdateService::class);
$this->repository = m::mock(DaemonKeyRepositoryInterface::class); $this->repository = m::mock(DaemonKeyRepositoryInterface::class);
$this->subuserRepository = m::mock(SubuserRepositoryInterface::class);
} }
/** /**
@ -154,6 +162,33 @@ class DaemonKeyProviderServiceTest extends TestCase
$this->assertEquals($key->secret, $response); $this->assertEquals($key->secret, $response);
} }
/**
* Test that a missing key is created for a subuser.
*/
public function testMissingKeyIsCreatedForSubuser()
{
$user = factory(User::class)->make(['root_admin' => 0]);
$server = factory(Server::class)->make();
$key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
$subuser = factory(Subuser::class)->make(['user_id' => $user->id, 'server_id' => $server->id]);
$this->repository->shouldReceive('findFirstWhere')->with([
['user_id', '=', $user->id],
['server_id', '=', $server->id],
])->once()->andThrow(new RecordNotFoundException);
$this->subuserRepository->shouldReceive('findFirstWhere')->once()->with([
['user_id', '=', $user->id],
['server_id', '=', $server->id],
])->andReturn($subuser);
$this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn($key->secret);
$response = $this->getService()->handle($server, $user, false);
$this->assertNotEmpty($response);
$this->assertEquals($key->secret, $response);
}
/** /**
* Test that an exception is thrown if the user should not get a key. * Test that an exception is thrown if the user should not get a key.
* *
@ -169,6 +204,11 @@ class DaemonKeyProviderServiceTest extends TestCase
['server_id', '=', $server->id], ['server_id', '=', $server->id],
])->once()->andThrow(new RecordNotFoundException); ])->once()->andThrow(new RecordNotFoundException);
$this->subuserRepository->shouldReceive('findFirstWhere')->once()->with([
['user_id', '=', $user->id],
['server_id', '=', $server->id],
])->andThrow(new RecordNotFoundException);
$this->getService()->handle($server, $user, false); $this->getService()->handle($server, $user, false);
} }
@ -179,6 +219,11 @@ class DaemonKeyProviderServiceTest extends TestCase
*/ */
private function getService(): DaemonKeyProviderService private function getService(): DaemonKeyProviderService
{ {
return new DaemonKeyProviderService($this->keyCreationService, $this->repository, $this->keyUpdateService); return new DaemonKeyProviderService(
$this->keyCreationService,
$this->repository,
$this->keyUpdateService,
$this->subuserRepository
);
} }
} }

View file

@ -116,8 +116,14 @@ class ServerCreationServiceTest extends TestCase
]); ]);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('isUniqueUuidCombo')
->once()
->with($this->getKnownUuid(), substr($this->getKnownUuid(), 0, 8))
->andReturn(true);
$this->repository->shouldReceive('create')->with(m::subset([ $this->repository->shouldReceive('create')->with(m::subset([
'uuid' => $this->getKnownUuid(), 'uuid' => $this->getKnownUuid(),
'uuidShort' => substr($this->getKnownUuid(), 0, 8),
'node_id' => $model->node_id, 'node_id' => $model->node_id,
'allocation_id' => $model->allocation_id, 'allocation_id' => $model->allocation_id,
'owner_id' => $model->owner_id, 'owner_id' => $model->owner_id,
@ -164,8 +170,14 @@ class ServerCreationServiceTest extends TestCase
$this->eggRepository->shouldReceive('setColumns->find')->once()->with($model->egg_id)->andReturn($eggModel); $this->eggRepository->shouldReceive('setColumns->find')->once()->with($model->egg_id)->andReturn($eggModel);
$this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([])); $this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([]));
$this->repository->shouldReceive('create')->once()->with(m::subset([ $this->repository->shouldReceive('isUniqueUuidCombo')
->once()
->with($this->getKnownUuid(), substr($this->getKnownUuid(), 0, 8))
->andReturn(true);
$this->repository->shouldReceive('create')->with(m::subset([
'uuid' => $this->getKnownUuid(), 'uuid' => $this->getKnownUuid(),
'uuidShort' => substr($this->getKnownUuid(), 0, 8),
'node_id' => $model->node_id, 'node_id' => $model->node_id,
'allocation_id' => $model->allocation_id, 'allocation_id' => $model->allocation_id,
'nest_id' => $model->nest_id, 'nest_id' => $model->nest_id,
@ -211,8 +223,14 @@ class ServerCreationServiceTest extends TestCase
$this->allocationSelectionService->shouldReceive('handle')->once()->withNoArgs()->andReturn($allocationModel); $this->allocationSelectionService->shouldReceive('handle')->once()->withNoArgs()->andReturn($allocationModel);
$this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([])); $this->validatorService->shouldReceive('setUserLevel->handle')->once()->andReturn(collect([]));
$this->repository->shouldReceive('create')->once()->with(m::subset([ $this->repository->shouldReceive('isUniqueUuidCombo')
->once()
->with($this->getKnownUuid(), substr($this->getKnownUuid(), 0, 8))
->andReturn(true);
$this->repository->shouldReceive('create')->with(m::subset([
'uuid' => $this->getKnownUuid(), 'uuid' => $this->getKnownUuid(),
'uuidShort' => substr($this->getKnownUuid(), 0, 8),
'node_id' => $model->node_id, 'node_id' => $model->node_id,
'allocation_id' => $model->allocation_id, 'allocation_id' => $model->allocation_id,
'nest_id' => $model->nest_id, 'nest_id' => $model->nest_id,
@ -244,6 +262,7 @@ class ServerCreationServiceTest extends TestCase
]); ]);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('isUniqueUuidCombo')->once()->andReturn(true);
$this->repository->shouldReceive('create')->once()->andReturn($model); $this->repository->shouldReceive('create')->once()->andReturn($model);
$this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->andReturn(1); $this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->andReturn(1);
$this->validatorService->shouldReceive('setUserLevel')->once()->andReturnSelf(); $this->validatorService->shouldReceive('setUserLevel')->once()->andReturnSelf();