Merge branch 'develop' into develop

This commit is contained in:
Dane Everitt 2017-11-18 16:29:52 -05:00 committed by GitHub
commit 9a588cb6dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 467 additions and 512 deletions

View File

@ -11,6 +11,13 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
* `[beta.1]` — Fixes missing check in environment setup that would leave the Hashids salt empty.
* `[beta.1]` — Fixes bug preventing loading of allocations when trying to create a new server.
* `[beta.1]` — Fixes bug causing inability to create new servers on the Panel.
* `[beta.1]` — Fixes bug causing inability to delete an allocation due to misconfigured JS.
* `[beta.1]` — Fixes bug causing inability to set the IP alias for an allocation to an empty value.
* `[beta.1]` — Fixes bug that caused startup changes to not propigate to the server correctly on the first save.
### Changed
* Moved Docker image setting to be on the startup management page for a server rather than the details page. This value changes based on the Nest and Egg that are selected.
* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database.
## v0.7.0-beta.1 (Derelict Dermodactylus)
### Added

View File

@ -15,6 +15,8 @@ use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
class CleanServiceBackupFilesCommand extends Command
{
const BACKUP_THRESHOLD_MINUTES = 5;
/**
* @var \Carbon\Carbon
*/
@ -58,7 +60,7 @@ class CleanServiceBackupFilesCommand extends Command
collect($files)->each(function ($file) {
$lastModified = $this->carbon->timestamp($this->disk->lastModified($file));
if ($lastModified->diffInMinutes($this->carbon->now()) > 5) {
if ($lastModified->diffInMinutes($this->carbon->now()) > self::BACKUP_THRESHOLD_MINUTES) {
$this->disk->delete($file);
$this->info(trans('command/messages.maintenance.deleting_service_backup', ['file' => $file]));
}

View File

@ -95,14 +95,15 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
public function getWithDatabases($id);
/**
* Return data about the daemon service in a consumable format.
* Get data for use when updating a server on the Daemon. Returns an array of
* the egg and pack UUID which are used for build and rebuild. Only loads relations
* if they are missing, or refresh is set to true.
*
* @param int $id
* @param \Pterodactyl\Models\Server $server
* @param bool $refresh
* @return array
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getDaemonServiceData($id);
public function getDaemonServiceData(Server $server, bool $refresh = false): array;
/**
* Return an array of server IDs that a given user can access based on owner and subuser permissions.

View File

@ -410,25 +410,6 @@ class ServersController extends Controller
return redirect()->route('admin.servers.view.details', $server->id);
}
/**
* Set the new docker container for a server.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function setContainer(Request $request, Server $server)
{
$this->detailsModificationService->setDockerImage($server, $request->input('docker_image'));
$this->alert->success(trans('admin/server.alerts.docker_image_updated'))->flash();
return redirect()->route('admin.servers.view.details', $server->id);
}
/**
* Toggles the install status for a server.
*

View File

@ -202,7 +202,7 @@ class LoginController extends Controller
return $this->sendFailedLoginResponse($request);
}
if (! $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 2)) {
if (! $G2FA->verifyKey(Crypt::decrypt($user->totp_secret), $request->input('2fa_token'), 2)) {
event(new \Illuminate\Auth\Events\Failed($user, $credentials));
return $this->sendFailedLoginResponse($request);

View File

@ -27,7 +27,6 @@ namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request;
use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Contracts\Session\Session;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Users\TwoFactorSetupService;
use Pterodactyl\Services\Users\ToggleTwoFactorService;
@ -52,11 +51,6 @@ class SecurityController extends Controller
*/
protected $repository;
/**
* @var \Illuminate\Contracts\Session\Session
*/
protected $session;
/**
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
*/
@ -72,7 +66,6 @@ class SecurityController extends Controller
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Session\Session $session
* @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository
* @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService
* @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService
@ -80,7 +73,6 @@ class SecurityController extends Controller
public function __construct(
AlertsMessageBag $alert,
ConfigRepository $config,
Session $session,
SessionRepositoryInterface $repository,
ToggleTwoFactorService $toggleTwoFactorService,
TwoFactorSetupService $twoFactorSetupService
@ -88,7 +80,6 @@ class SecurityController extends Controller
$this->alert = $alert;
$this->config = $config;
$this->repository = $repository;
$this->session = $session;
$this->toggleTwoFactorService = $toggleTwoFactorService;
$this->twoFactorSetupService = $twoFactorSetupService;
}
@ -122,7 +113,9 @@ class SecurityController extends Controller
*/
public function generateTotp(Request $request)
{
return response()->json($this->twoFactorSetupService->handle($request->user()));
return response()->json([
'qrImage' => $this->twoFactorSetupService->handle($request->user()),
]);
}
/**

View File

@ -19,7 +19,7 @@ class AllocationAliasFormRequest extends AdminFormRequest
public function rules()
{
return [
'alias' => 'required|nullable|string',
'alias' => 'present|nullable|string',
'allocation_id' => 'required|numeric|exists:allocations,id',
];
}

View File

@ -60,7 +60,7 @@ class Allocation extends Model implements CleansAttributes, ValidableContract
'node_id' => 'exists:nodes,id',
'ip' => 'ip',
'port' => 'numeric|between:1024,65553',
'alias' => 'string',
'ip_alias' => 'nullable|string',
'server_id' => 'nullable|exists:servers,id',
];

View File

@ -63,6 +63,7 @@ class User extends Model implements
'language',
'use_totp',
'totp_secret',
'totp_authenticated_at',
'gravatar',
'root_admin',
];
@ -78,6 +79,11 @@ class User extends Model implements
'gravatar' => 'boolean',
];
/**
* @var array
*/
protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'totp_authenticated_at'];
/**
* The attributes excluded from the model's JSON form.
*

View File

@ -13,7 +13,6 @@ use Cache;
use Carbon;
use Pterodactyl\Models\User;
use Pterodactyl\Models\APIKey as Key;
use Pterodactyl\Models\APIPermission as Permission;
class APIKeyPolicy
{

View File

@ -2,7 +2,6 @@
namespace Pterodactyl\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider

View File

@ -2,7 +2,6 @@
namespace Pterodactyl\Providers;
use Pterodactyl\Models\User;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

View File

@ -10,7 +10,7 @@
namespace Pterodactyl\Repositories\Eloquent;
use Webmozart\Assert\Assert;
use Pterodactyl\Repository\Repository;
use Pterodactyl\Repositories\Repository;
use Illuminate\Database\Query\Expression;
use Pterodactyl\Contracts\Repository\RepositoryInterface;
use Pterodactyl\Exceptions\Model\DataValidationException;

View File

@ -18,6 +18,9 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
{
use Searchable;
const THRESHOLD_PERCENTAGE_LOW = 75;
const THRESHOLD_PERCENTAGE_MEDIUM = 90;
/**
* {@inheritdoc}
*/
@ -56,7 +59,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= 75) ? 'green' : (($percent > 90) ? 'red' : 'yellow'),
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})
@ -104,8 +107,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
$instance->setRelation(
'allocations',
$instance->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')
->with('server')->paginate(50)
$instance->allocations()->orderBy('ip', 'asc')->orderBy('port', 'asc')->with('server')->paginate(50)
);
return $instance;

View File

@ -187,21 +187,27 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
}
/**
* {@inheritdoc}
* Get data for use when updating a server on the Daemon. Returns an array of
* the egg and pack UUID which are used for build and rebuild. Only loads relations
* if they are missing, or refresh is set to true.
*
* @param \Pterodactyl\Models\Server $server
* @param bool $refresh
* @return array
*/
public function getDaemonServiceData($id)
public function getDaemonServiceData(Server $server, bool $refresh = false): array
{
Assert::integerish($id, 'First argument passed to getDaemonServiceData must be integer, received %s.');
if (! $server->relationLoaded('egg') || $refresh) {
$server->load('egg');
}
$instance = $this->getBuilder()->with('egg.nest', 'pack')->find($id, $this->getColumns());
if (! $instance) {
throw new RecordNotFoundException();
if (! $server->relationLoaded('pack') || $refresh) {
$server->load('pack');
}
return [
'type' => $instance->egg->nest->folder,
'option' => $instance->egg->tag,
'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null,
'egg' => $server->getRelation('egg')->uuid,
'pack' => is_null($server->getRelation('pack')) ? null : $server->getRelation('pack')->uuid,
];
}

View File

@ -7,7 +7,7 @@
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Repository;
namespace Pterodactyl\Repositories;
use Illuminate\Foundation\Application;
use Pterodactyl\Contracts\Repository\RepositoryInterface;

View File

@ -16,6 +16,8 @@ use Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException;
class TaskCreationService
{
const MAX_INTERVAL_TIME_SECONDS = 900;
/**
* @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
*/
@ -50,7 +52,7 @@ class TaskCreationService
$schedule = ($schedule instanceof Schedule) ? $schedule->id : $schedule;
$delay = $data['time_interval'] === 'm' ? $data['time_value'] * 60 : $data['time_value'];
if ($delay > 900) {
if ($delay > self::MAX_INTERVAL_TIME_SECONDS) {
throw new TaskIntervalTooLongException(trans('exceptions.tasks.chain_interval_too_long'));
}

View File

@ -99,14 +99,17 @@ class StartupModificationService
});
}
$daemonData = ['build' => [
'env|overwrite' => $this->environmentService->handle($server),
]];
$daemonData = [];
if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) {
$this->updateAdministrativeSettings($data, $server, $daemonData);
}
$daemonData = array_merge_recursive($daemonData, [
'build' => [
'env|overwrite' => $this->environmentService->handle($server),
],
]);
try {
$this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($daemonData);
} catch (RequestException $exception) {
@ -136,17 +139,15 @@ class StartupModificationService
'egg_id' => array_get($data, 'egg_id', $server->egg_id),
'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null,
'skip_scripts' => isset($data['skip_scripts']),
'image' => array_get($data, 'docker_image', $server->image),
]);
if (
$server->nest_id != array_get($data, 'nest_id', $server->nest_id) ||
$server->egg_id != array_get($data, 'egg_id', $server->egg_id) ||
$server->pack_id != array_get($data, 'pack_id', $server->pack_id)
) {
$daemonData['service'] = array_merge(
$this->repository->withColumns(['id', 'egg_id', 'pack_id'])->getDaemonServiceData($server->id),
$daemonData = array_merge($daemonData, [
'build' => ['image' => $server->image],
'service' => array_merge(
$this->repository->getDaemonServiceData($server, true),
['skip_scripts' => isset($data['skip_scripts'])]
);
}
),
]);
}
}

View File

@ -1,66 +1,82 @@
<?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\Services\Users;
use Carbon\Carbon;
use Pterodactyl\Models\User;
use PragmaRX\Google2FA\Contracts\Google2FA;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
class ToggleTwoFactorService
{
/**
* @var \PragmaRX\Google2FA\Contracts\Google2FA
* @var \Illuminate\Contracts\Config\Repository
*/
protected $google2FA;
private $config;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/**
* @var \PragmaRX\Google2FA\Google2FA
*/
private $google2FA;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
protected $repository;
private $repository;
/**
* ToggleTwoFactorService constructor.
*
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
public function __construct(
Encrypter $encrypter,
Google2FA $google2FA,
Repository $config,
UserRepositoryInterface $repository
) {
$this->config = $config;
$this->encrypter = $encrypter;
$this->google2FA = $google2FA;
$this->repository = $repository;
}
/**
* @param int|\Pterodactyl\Models\User $user
* @param string $token
* @param null|bool $toggleState
* Toggle 2FA on an account only if the token provided is valid.
*
* @param \Pterodactyl\Models\User $user
* @param string $token
* @param bool|null $toggleState
* @return bool
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
*/
public function handle($user, $token, $toggleState = null)
public function handle(User $user, string $token, bool $toggleState = null): bool
{
if (! $user instanceof User) {
$user = $this->repository->find($user);
}
$window = $this->config->get('pterodactyl.auth.2fa.window');
$secret = $this->encrypter->decrypt($user->totp_secret);
if (! $this->google2FA->verifyKey($user->totp_secret, $token, 2)) {
$isValidToken = $this->google2FA->verifyKey($secret, $token, $window);
if (! $isValidToken) {
throw new TwoFactorAuthenticationTokenInvalid;
}
$this->repository->withoutFresh()->update($user->id, [
'totp_authenticated_at' => Carbon::now(),
'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState),
]);

View File

@ -10,7 +10,8 @@
namespace Pterodactyl\Services\Users;
use Pterodactyl\Models\User;
use PragmaRX\Google2FA\Contracts\Google2FA;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
@ -19,58 +20,62 @@ class TwoFactorSetupService
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
private $config;
/**
* @var \PragmaRX\Google2FA\Contracts\Google2FA
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
protected $google2FA;
private $encrypter;
/**
* @var \PragmaRX\Google2FA\Google2FA
*/
private $google2FA;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
protected $repository;
private $repository;
/**
* TwoFactorSetupService constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
public function __construct(
ConfigRepository $config,
Encrypter $encrypter,
Google2FA $google2FA,
UserRepositoryInterface $repository
) {
$this->config = $config;
$this->encrypter = $encrypter;
$this->google2FA = $google2FA;
$this->repository = $repository;
}
/**
* Generate a 2FA token and store it in the database.
* Generate a 2FA token and store it in the database before returning the
* QR code image.
*
* @param int|\Pterodactyl\Models\User $user
* @return array
* @param \Pterodactyl\Models\User $user
* @return string
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($user)
public function handle(User $user): string
{
if (! $user instanceof User) {
$user = $this->repository->find($user);
}
$secret = $this->google2FA->generateSecretKey();
$secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes'));
$image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret);
$this->repository->withoutFresh()->update($user->id, ['totp_secret' => $secret]);
$this->repository->withoutFresh()->update($user->id, [
'totp_secret' => $this->encrypter->encrypt($secret),
]);
return [
'qrImage' => $image,
'secret' => $secret,
];
return $image;
}
}

View File

@ -11,7 +11,6 @@ namespace Pterodactyl\Transformers\Admin;
use Illuminate\Http\Request;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Models\Permission;
use League\Fractal\TransformerAbstract;
class SubuserTransformer extends TransformerAbstract

View File

@ -10,7 +10,6 @@
namespace Pterodactyl\Transformers\User;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Models\Permission;
use League\Fractal\TransformerAbstract;
class SubuserTransformer extends TransformerAbstract

View File

@ -31,7 +31,7 @@
"mtdowling/cron-expression": "^1.2",
"nesbot/carbon": "^1.22",
"nicolaslopezj/searchable": "^1.9",
"pragmarx/google2fa": "^1.0",
"pragmarx/google2fa": "^2.0",
"predis/predis": "^1.1",
"prologue/alerts": "^0.4",
"ramsey/uuid": "^3.7",
@ -46,7 +46,7 @@
"require-dev": {
"barryvdh/laravel-debugbar": "^2.4",
"barryvdh/laravel-ide-helper": "^2.4",
"friendsofphp/php-cs-fixer": "^2.4",
"friendsofphp/php-cs-fixer": "^2.8.0",
"fzaninotto/faker": "^1.6",
"mockery/mockery": "^0.9",
"php-mock/php-mock-phpunit": "^1.1",

189
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "3758867d4fb2d20e4b4e45b7c410f79b",
"content-hash": "a393763d136e25a93fd5b636229496cf",
"packages": [
{
"name": "appstract/laravel-blade-directives",
@ -61,16 +61,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.36.37",
"version": "3.38.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "a6d7fd9f32c63d018a6603a36174b4cb971fccd9"
"reference": "9f704274f4748d2039a16d45b3388ed8dde74e89"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a6d7fd9f32c63d018a6603a36174b4cb971fccd9",
"reference": "a6d7fd9f32c63d018a6603a36174b4cb971fccd9",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9f704274f4748d2039a16d45b3388ed8dde74e89",
"reference": "9f704274f4748d2039a16d45b3388ed8dde74e89",
"shasum": ""
},
"require": {
@ -137,61 +137,7 @@
"s3",
"sdk"
],
"time": "2017-11-03T16:39:35+00:00"
},
{
"name": "christian-riesen/base32",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/ChristianRiesen/base32.git",
"reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa",
"reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"satooshi/php-coveralls": "0.*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Base32\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Riesen",
"email": "chris.riesen@gmail.com",
"homepage": "http://christianriesen.com",
"role": "Developer"
}
],
"description": "Base32 encoder/decoder according to RFC 4648",
"homepage": "https://github.com/ChristianRiesen/base32",
"keywords": [
"base32",
"decode",
"encode",
"rfc4648"
],
"time": "2016-05-05T11:49:03+00:00"
"time": "2017-11-09T19:15:59+00:00"
},
{
"name": "daneeveritt/login-notifications",
@ -2055,6 +2001,68 @@
],
"time": "2017-11-04T11:48:34+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4",
"reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4",
"shasum": ""
},
"require": {
"php": "^7"
},
"require-dev": {
"phpunit/phpunit": "^6",
"vimeo/psalm": "^0.3|^1"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"time": "2017-09-22T14:55:37+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.11",
@ -2105,26 +2113,28 @@
},
{
"name": "pragmarx/google2fa",
"version": "v1.0.1",
"version": "v2.0.6",
"source": {
"type": "git",
"url": "https://github.com/antonioribeiro/google2fa.git",
"reference": "b346dc138339b745c5831405d00cff7c1351aa0d"
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d",
"reference": "b346dc138339b745c5831405d00cff7c1351aa0d",
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/bc2d654305e4d09254125f8cd390a7fbc4742d46",
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46",
"shasum": ""
},
"require": {
"christian-riesen/base32": "~1.3",
"paragonie/constant_time_encoding": "~1.0|~2.0",
"paragonie/random_compat": "~1.4|~2.0",
"php": ">=5.4",
"symfony/polyfill-php56": "~1.2"
},
"require-dev": {
"phpspec/phpspec": "~2.1"
"bacon/bacon-qr-code": "~1.0",
"phpspec/phpspec": "~2.1",
"phpunit/phpunit": "~4"
},
"suggest": {
"bacon/bacon-qr-code": "Required to generate inline QR Codes."
@ -2132,11 +2142,8 @@
"type": "library",
"extra": {
"component": "package",
"frameworks": [
"Laravel"
],
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "2.0-dev"
}
},
"autoload": {
@ -2157,12 +2164,13 @@
],
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
"keywords": [
"2fa",
"Authentication",
"Two Factor Authentication",
"google2fa",
"laravel"
],
"time": "2016-07-18T20:25:04+00:00"
"time": "2017-09-12T06:55:05+00:00"
},
{
"name": "predis/predis",
@ -3796,16 +3804,16 @@
},
{
"name": "watson/validating",
"version": "3.1.1",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/dwightwatson/validating.git",
"reference": "ade13078bf2e820e244603446114a28eda51b08c"
"reference": "22edd06d45893f5d4f79c9e901bd7fbce174a79f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dwightwatson/validating/zipball/ade13078bf2e820e244603446114a28eda51b08c",
"reference": "ade13078bf2e820e244603446114a28eda51b08c",
"url": "https://api.github.com/repos/dwightwatson/validating/zipball/22edd06d45893f5d4f79c9e901bd7fbce174a79f",
"reference": "22edd06d45893f5d4f79c9e901bd7fbce174a79f",
"shasum": ""
},
"require": {
@ -3842,7 +3850,7 @@
"laravel",
"validation"
],
"time": "2017-10-08T22:42:01+00:00"
"time": "2017-11-06T21:35:49+00:00"
},
{
"name": "webmozart/assert",
@ -4291,16 +4299,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v2.8.0",
"version": "v2.8.1",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "89e7b083f27241e03dd776cb8d6781c77e341db6"
"reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/89e7b083f27241e03dd776cb8d6781c77e341db6",
"reference": "89e7b083f27241e03dd776cb8d6781c77e341db6",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/04f71e56e03ba2627e345e8c949c80dcef0e683e",
"reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e",
"shasum": ""
},
"require": {
@ -4367,7 +4375,7 @@
}
],
"description": "A tool to automatically fix PHP code style",
"time": "2017-11-03T02:21:46+00:00"
"time": "2017-11-09T13:31:39+00:00"
},
{
"name": "fzaninotto/faker",
@ -4421,23 +4429,23 @@
},
{
"name": "gecko-packages/gecko-php-unit",
"version": "v2.2",
"version": "v3.0",
"source": {
"type": "git",
"url": "https://github.com/GeckoPackages/GeckoPHPUnit.git",
"reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1"
"reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/ab525fac9a9ffea219687f261b02008b18ebf2d1",
"reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1",
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/6a866551dffc2154c1b091bae3a7877d39c25ca3",
"reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3",
"shasum": ""
},
"require": {
"php": "^5.3.6 || ^7.0"
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.4.3"
"phpunit/phpunit": "^6.0"
},
"suggest": {
"ext-dom": "When testing with xml.",
@ -4445,6 +4453,11 @@
"phpunit/phpunit": "This is an extension for it so make sure you have it some way."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"GeckoPackages\\PHPUnit\\": "src/PHPUnit"
@ -4461,7 +4474,7 @@
"filesystem",
"phpunit"
],
"time": "2017-08-23T07:39:54+00:00"
"time": "2017-08-23T07:46:41+00:00"
},
{
"name": "hamcrest/hamcrest-php",

View File

@ -171,7 +171,6 @@ return [
/*
* Additional Dependencies
*/
PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class,
igaster\laravelTheme\themeServiceProvider::class,
Prologue\Alerts\AlertsServiceProvider::class,
Krucas\Settings\Providers\SettingsServiceProvider::class,
@ -213,7 +212,6 @@ return [
'File' => Illuminate\Support\Facades\File::class,
'Fractal' => Spatie\Fractal\FractalFacade::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Input' => Illuminate\Support\Facades\Input::class,
'Inspiring' => Illuminate\Foundation\Inspiring::class,

View File

@ -23,6 +23,11 @@ return [
*/
'auth' => [
'notifications' => env('LOGIN_NOTIFICATIONS', false),
'2fa' => [
'bytes' => 32,
'window' => env('APP_2FA_WINDOW', 4),
'verify_newer' => true,
],
],
/*

View File

@ -0,0 +1,60 @@
<?php
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Add2FaLastAuthorizationTimeColumn extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->text('totp_secret')->nullable()->change();
$table->timestampTz('totp_authenticated_at')->after('totp_secret')->nullable();
});
DB::transaction(function () {
DB::table('users')->get()->each(function ($user) {
if (is_null($user->totp_secret)) {
return;
}
DB::table('users')->where('id', $user->id)->update([
'totp_secret' => Crypt::encrypt($user->totp_secret),
'updated_at' => Carbon::now()->toIso8601String(),
]);
});
});
}
/**
* Reverse the migrations.
*/
public function down()
{
DB::transaction(function () {
DB::table('users')->get()->each(function ($user) {
if (is_null($user->totp_secret)) {
return;
}
DB::table('users')->where('id', $user->id)->update([
'totp_secret' => Crypt::decrypt($user->totp_secret),
'updated_at' => Carbon::now()->toIso8601String(),
]);
});
});
DB::statement('ALTER TABLE users MODIFY totp_secret CHAR(16) DEFAULT NULL');
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('totp_authenticated_at');
});
}
}

View File

@ -420,3 +420,7 @@ label.control-label > span.field-optional:before {
content: "optional";
color: #bbbbbb;
}
.pagination > li > a, .pagination > li > span {
padding: 3px 10px !important;
}

View File

@ -42,7 +42,6 @@ var TwoFactorModal = (function () {
$('#qr_image_insert').attr('src', image.src).slideDown();
});
});
$('#2fa_secret_insert').html(data.secret);
$('#open2fa').modal('show');
}).fail(function (jqXHR) {
alert('An error occured while attempting to load the 2FA setup modal. Please try again.');

View File

@ -6,7 +6,7 @@ return [
'authentication_required' => 'La autenticación es necesaria para continuar.',
'remember_me' => 'Recuérdame',
'sign_in' => 'Iniciar Sesión',
'forgot_password' => 'Olvidé mi contraseña!',
'forgot_password' => '¡Olvidé mi contraseña!',
'request_reset_text' => '¿Olvidaste tu contraseña? No es el fin del mundo, sólo proporcione su correo electrónico a continuación.',
'reset_password_text' => 'Restablece la contraseña de su cuenta.',
'reset_password' => 'Restablece contraseña de cuenta.',

View File

@ -259,7 +259,7 @@ return [
'header' => 'El Administrador De Archivos',
'header_sub' => 'Administrar todos tus archivos directamente desde la web.',
'loading' => 'La carga inicial de la estructura del archivo, esto puede tardar unos segundos.',
'path' => 'Cuando la configuración de rutas de archivo en su servidor de plugins o configuración que debe utilizar :path de acceso como base de la ruta. El tamaño máximo para la web basado en la carga de archivos a este nodo es :tamaño de la.',
'path' => 'Cuando la configuración de rutas de archivo en su servidor de plugins o configuración que debe utilizar :path de acceso como base de la ruta. El tamaño máximo para la web basado en la carga de archivos a este nodo es :size de la.',
'seconds_ago' => 'hace segundos',
'file_name' => 'Nombre De Archivo',
'size' => 'Tamaño',

View File

@ -72,9 +72,11 @@
@endforeach
</table>
</div>
<div class="box-footer text-center">
{{ $node->allocations->render() }}
</div>
@if($node->allocations->hasPages())
<div class="box-footer text-center">
{{ $node->allocations->render() }}
</div>
@endif
</div>
</div>
<div class="col-sm-4">
@ -177,7 +179,7 @@
}, function () {
$.ajax({
method: 'DELETE',
url: Router.route('admin.nodes.view.allocation.removeSingle', { id: Pterodactyl.node.id, allocation: allocation }),
url: Router.route('admin.nodes.view.allocation.removeSingle', { node: Pterodactyl.node.id, allocation: allocation }),
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
}).done(function (data) {
element.parent().parent().addClass('warning').delay(100).fadeOut();

View File

@ -39,7 +39,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Base Information</h3>
@ -63,15 +63,6 @@
<textarea name="description" rows="3" class="form-control">{{ old('description', $server->description) }}</textarea>
<p class="text-muted small">A brief description of this server.</p>
</div>
<div class="form-group">
<label for="name" class="control-label">Daemon Secret Token</label>
<input type="text" disabled value="{{ $server->daemonSecret }}" class="form-control" />
<p class="text-muted small">This token should not be shared with anyone as it has full control over this server.</p>
</div>
<div class="form-group">
<input type="checkbox" name="reset_token" id="pResetToken"/> <label for="pResetToken">Reset Daemon Token</label>
<p class="text-muted small">Resetting this token will cause any requests using the old token to fail.</p>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
@ -81,27 +72,6 @@
</form>
</div>
</div>
<div class="col-sm-6">
<div class="box box-success">
<div class="box-header with-border">
<h3 class="box-title">Container Setup</h3>
</div>
<form action="{{ route('admin.servers.view.details.container', $server->id) }}" method="POST">
<div class="box-body">
<div class="form-group">
<label for="name" class="control-label">Docker Image</label>
<input type="text" name="docker_image" value="{{ $server->image }}" class="form-control" />
<p class="text-muted small">The docker image to use for this server. The default image for this service and option combination is <code>{{ $server->egg->docker_image }}</code>.</p>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
{!! method_field('PATCH') !!}
<input type="submit" class="btn btn-sm btn-primary" value="Update Docker Container" />
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@ -109,6 +109,18 @@
</div>
</div>
</div>
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Docker Container Configuration</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pDockerImage" class="control-label">Image</label>
<input type="text" name="docker_image" id="pDockerImage" value="{{ $server->image }}" class="form-control" />
<p class="text-muted small">The Docker image to use for this server. The default image for the selected egg is <code id="setDefaultImage"></code>.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="row" id="appendVariablesTo"></div>
@ -122,24 +134,9 @@
{!! Theme::js('vendor/lodash/lodash.js') !!}
<script>
$(document).ready(function () {
$('#pNestId').select2({
placeholder: 'Select a Nest',
}).change();
$('#pEggId').select2({
placeholder: 'Select a Nest Egg',
});
$('#pPackId').select2({
placeholder: 'Select a Service Pack',
});
$('input[data-action="match-regex"]').on('keyup', function (event) {
if (! $(this).data('regex')) return;
var input = $(this).val();
var regex = new RegExp($(this).data('regex').replace(/^\/|\/$/g, ''));
$(this).parent().parent().removeClass('has-success has-error').addClass((! regex.test(input)) ? 'has-error' : 'has-success');
});
$('#pNestId').select2({placeholder: 'Select a Nest'}).change();
$('#pEggId').select2({placeholder: 'Select a Nest Egg'});
$('#pPackId').select2({placeholder: 'Select a Service Pack'});
});
</script>
<script>
@ -158,7 +155,11 @@
var parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null);
var objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null);
$('#pDefaultContainer').val(_.get(objectChain, 'docker_image', 'not defined!'));
$('#setDefaultImage').html(_.get(objectChain, 'docker_image', 'undefined'));
$('#pDockerImage').val(_.get(objectChain, 'docker_image', 'undefined'));
if (objectChain.id === parseInt('{{ $server->egg_id }}')) {
$('#pDockerImage').val('{{ $server->image }}');
}
if (!_.get(objectChain, 'startup', false)) {
$('#pDefaultStartupCommand').val(_.get(parentChain, 'startup', 'ERROR: Startup Not Defined!'));
@ -192,7 +193,7 @@
<h3 class="box-title">' + isRequired + item.name + '</h3> \
</div> \
<div class="box-body"> \
<input data-action="match-regex" name="environment[' + item.env_variable + ']" class="form-control" type="text" value="' + setValue + '" /> \
<input name="environment[' + item.env_variable + ']" class="form-control" type="text" id="egg_variable_' + item.env_variable + '" /> \
<p class="no-margin small text-muted">' + item.description + '</p> \
</div> \
<div class="box-footer"> \
@ -202,6 +203,7 @@
</div> \
</div>';
$('#appendVariablesTo').append(dataAppend);
$('#appendVariablesTo').find('#egg_variable_' + item.env_variable).val(setValue);
});
});
</script>

View File

@ -34,24 +34,6 @@
<p class="text-muted"><small>This is the name that is used throughout the panel and in emails sent to clients.</small></p>
</div>
</div>
{{-- <div class="form-group col-md-6">
<label class="control-label">Default Language:</label>
<div>
<select name="default_language" class="form-control">
<option value="de" @if(Settings::get('default_language') === 'de')selected @endif>Deutsch</option>
<option value="en" @if(Settings::get('default_language', 'en') === 'en')selected @endif>English</option>
<option value="es" @if(Settings::get('default_language') === 'es')selected @endif>Espa&ntilde;ol</option>
<option value="fr" @if(Settings::get('default_language') === 'fr')selected @endif>Fran&ccedil;ais</option>
<option value="it" @if(Settings::get('default_language') === 'it')selected @endif>Italiano</option>
<option value="pl" @if(Settings::get('default_language') === 'pl')selected @endif>Polski</option>
<option value="pt" @if(Settings::get('default_language') === 'pt')selected @endif>Portugu&ecirc;s</option>
<option value="ru" @if(Settings::get('default_language') === 'ru')selected @endif>&#1088;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;</option>
<option value="se" @if(Settings::get('default_language') === 'se')selected @endif>Svenska</option>
<option value="zh" @if(Settings::get('default_language') === 'zh')selected @endif>&#20013;&#22269;&#30340;的</option>
</select>
<p class="text-muted"><small>This is the default language that all clients will use unless they manually change it.</small></p>
</div>
</div> --}}
<div class="form-group col-md-6">
<label class="control-label">2FA Required</label>
<div>
@ -66,13 +48,13 @@
<input type="radio" name="2fa" autocomplete="off" value="2" @if (old('2fa', Settings::get('2fa', 0)) == 2) checked @endif> Everybody
</label>
</div>
<p class="text-muted"><small>Require your administrators or users to have 2FA enabled. Users include Admins. Everybody includes Sub Users.</small></p>
<p class="text-muted"><small>For improved security you can require all administrators to have 2-Factor authentication enabled, or even require it for all users on the Panel.</small></p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">In order to modify your SMTP settings for sending mail you will need to run <code>php artisan pterodactyl:mail</code> in this project's root folder.</div>
<div class="alert alert-info">In order to modify your SMTP settings for sending mail you will need to run <code>php artisan p:environment:mail</code> in this project's root folder.</div>
</div>
</div>
</div>

View File

@ -106,8 +106,8 @@
<div class="col-md-12" id="notice_box_2fa" style="display:none;"></div>
</div>
<div class="row">
<div class="col-md-6">
<center><span id="hide_img_load"><i class="fa fa-spinner fa-spin"></i> Loading QR Code...</span><img src="" id="qr_image_insert" style="display:none;"/><br /><code id="2fa_secret_insert"></code></center>
<div class="col-md-6 text-center">
<span id="hide_img_load"><i class="fa fa-spinner fa-spin"></i> Loading QR Code...</span><img src="" id="qr_image_insert" style="display:none;"/>
</div>
<div class="col-md-6">
<div class="alert alert-info">@lang('base.security.2fa_checkpoint_help')</div>

View File

@ -2,7 +2,7 @@
<div class="row">
<div class="col-xs-12">
<div class="alert alert-warning no-margin-bottom">
<i class="fa fa-warning"></i> You are running a beta version of Pterodactyl Panel. Not all features are complete and bugs should be expected. Please report any bugs on Discord or via our Github issue tracker.
<i class="fa fa-warning"></i> You are running a beta version of Pterodactyl Panel. Not all features are complete and bugs should be expected. Please report any bugs on <a href="https://discord.gg/NgqGpmS">Discord</a> or via our <a href="https://github.com/Pterodactyl/Panel/issues/new">GitHub Issue Tracker</a>.
</div>
</div>
</div>

View File

@ -1,36 +0,0 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="page-item disabled"><span class="page-link">&laquo;</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Pagination Elements -->
@foreach ($elements as $element)
<!-- "Three Dots" Separator -->
@if (is_string($element))
<li class="page-item disabled"><span class="page-link">{{ $element }}</span></li>
@endif
<!-- Array Of Links -->
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="page-item active"><span class="page-link">{{ $page }}</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="page-item disabled"><span class="page-link">&raquo;</span></li>
@endif
</ul>
@endif

View File

@ -1,5 +1,5 @@
@if ($paginator->lastPage() > 1)
<ul class="pagination">
<ul class="pagination pull-right no-margin">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
{{-- <li class="disabled"><span>&laquo;</span></li> --}}

View File

@ -1,17 +0,0 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="page-item disabled"><span class="page-link">&laquo;</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="page-item disabled"><span class="page-link">&raquo;</span></li>
@endif
</ul>
@endif

View File

@ -1,17 +0,0 @@
@if ($paginator->count() > 1)
<ul class="pagination">
<!-- Previous Page Link -->
@if ($paginator->onFirstPage())
<li class="disabled"><span>&laquo;</span></li>
@else
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
@endif
<!-- Next Page Link -->
@if ($paginator->hasMorePages())
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
@else
<li class="disabled"><span>&raquo;</span></li>
@endif
</ul>
@endif

View File

@ -105,7 +105,6 @@ Route::group(['prefix' => 'servers'], function () {
Route::post('/view/{server}/delete', 'ServersController@delete');
Route::patch('/view/{server}/details', 'ServersController@setDetails');
Route::patch('/view/{server}/details/container', 'ServersController@setContainer')->name('admin.servers.view.details.container');
Route::patch('/view/{server}/database', 'ServersController@resetDatabasePassword');
Route::delete('/view/{server}/database/{database}/delete', 'ServersController@deleteDatabase')->name('admin.servers.view.database.delete');

View File

@ -1,69 +1,41 @@
<?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 Tests\Unit\Http\Controllers\Base;
use Mockery as m;
use Tests\TestCase;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Config\Repository;
use Tests\Assertions\ControllerAssertionsTrait;
use Tests\Unit\Http\Controllers\ControllerTestCase;
use Pterodactyl\Services\Users\TwoFactorSetupService;
use Pterodactyl\Services\Users\ToggleTwoFactorService;
use Pterodactyl\Http\Controllers\Base\SecurityController;
use Pterodactyl\Contracts\Repository\SessionRepositoryInterface;
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
class SecurityControllerTest extends TestCase
class SecurityControllerTest extends ControllerTestCase
{
use ControllerAssertionsTrait;
/**
* @var \Prologue\Alerts\AlertsMessageBag
* @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock
*/
protected $alert;
/**
* @var \Illuminate\Contracts\Config\Repository
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
protected $config;
/**
* @var \Pterodactyl\Http\Controllers\Base\SecurityController
*/
protected $controller;
/**
* @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface
* @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* @var \Illuminate\Contracts\Session\Session
*/
protected $session;
/**
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService|\Mockery\Mock
*/
protected $toggleTwoFactorService;
/**
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
* @var \Pterodactyl\Services\Users\TwoFactorSetupService|\Mockery\Mock
*/
protected $twoFactorSetupService;
@ -77,19 +49,8 @@ class SecurityControllerTest extends TestCase
$this->alert = m::mock(AlertsMessageBag::class);
$this->config = m::mock(Repository::class);
$this->repository = m::mock(SessionRepositoryInterface::class);
$this->request = m::mock(Request::class);
$this->session = m::mock(Session::class);
$this->toggleTwoFactorService = m::mock(ToggleTwoFactorService::class);
$this->twoFactorSetupService = m::mock(TwoFactorSetupService::class);
$this->controller = new SecurityController(
$this->alert,
$this->config,
$this->session,
$this->repository,
$this->toggleTwoFactorService,
$this->twoFactorSetupService
);
}
/**
@ -97,13 +58,12 @@ class SecurityControllerTest extends TestCase
*/
public function testIndexControllerWithDatabaseDriver()
{
$model = factory(User::class)->make();
$model = $this->setRequestUser();
$this->config->shouldReceive('get')->with('session.driver')->once()->andReturn('database');
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
$this->repository->shouldReceive('getUserSessions')->with($model->id)->once()->andReturn(['sessions']);
$response = $this->controller->index($this->request);
$response = $this->getController()->index($this->request);
$this->assertIsViewResponse($response);
$this->assertViewNameEquals('base.security', $response);
$this->assertViewHasKey('sessions', $response);
@ -117,7 +77,7 @@ class SecurityControllerTest extends TestCase
{
$this->config->shouldReceive('get')->with('session.driver')->once()->andReturn('redis');
$response = $this->controller->index($this->request);
$response = $this->getController()->index($this->request);
$this->assertIsViewResponse($response);
$this->assertViewNameEquals('base.security', $response);
$this->assertViewHasKey('sessions', $response);
@ -129,14 +89,13 @@ class SecurityControllerTest extends TestCase
*/
public function testGenerateTotpController()
{
$model = factory(User::class)->make();
$model = $this->setRequestUser();
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
$this->twoFactorSetupService->shouldReceive('handle')->with($model)->once()->andReturn(['string']);
$this->twoFactorSetupService->shouldReceive('handle')->with($model)->once()->andReturn('qrCodeImage');
$response = $this->controller->generateTotp($this->request);
$response = $this->getController()->generateTotp($this->request);
$this->assertIsJsonResponse($response);
$this->assertResponseJsonEquals(['string'], $response);
$this->assertResponseJsonEquals(['qrImage' => 'qrCodeImage'], $response);
}
/**
@ -144,13 +103,12 @@ class SecurityControllerTest extends TestCase
*/
public function testDisableTotpControllerSuccess()
{
$model = factory(User::class)->make();
$model = $this->setRequestUser();
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
$this->request->shouldReceive('input')->with('token')->once()->andReturn('testToken');
$this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()->andReturnNull();
$response = $this->controller->disableTotp($this->request);
$response = $this->getController()->disableTotp($this->request);
$this->assertIsRedirectResponse($response);
$this->assertRedirectRouteEquals('account.security', $response);
}
@ -160,16 +118,14 @@ class SecurityControllerTest extends TestCase
*/
public function testDisableTotpControllerWhenExceptionIsThrown()
{
$model = factory(User::class)->make();
$model = $this->setRequestUser();
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
$this->request->shouldReceive('input')->with('token')->once()->andReturn('testToken');
$this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()
->andThrow(new TwoFactorAuthenticationTokenInvalid);
$this->alert->shouldReceive('danger')->with(trans('base.security.2fa_disable_error'))->once()->andReturnSelf()
->shouldReceive('flash')->withNoArgs()->once()->andReturnNull();
$this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()->andThrow(new TwoFactorAuthenticationTokenInvalid);
$this->alert->shouldReceive('danger')->with(trans('base.security.2fa_disable_error'))->once()->andReturnSelf();
$this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull();
$response = $this->controller->disableTotp($this->request);
$response = $this->getController()->disableTotp($this->request);
$this->assertIsRedirectResponse($response);
$this->assertRedirectRouteEquals('account.security', $response);
}
@ -179,13 +135,28 @@ class SecurityControllerTest extends TestCase
*/
public function testRevokeController()
{
$model = factory(User::class)->make();
$model = $this->setRequestUser();
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
$this->repository->shouldReceive('deleteUserSession')->with($model->id, 123)->once()->andReturnNull();
$response = $this->controller->revoke($this->request, 123);
$response = $this->getController()->revoke($this->request, 123);
$this->assertIsRedirectResponse($response);
$this->assertRedirectRouteEquals('account.security', $response);
}
/**
* Return an instance of the controller for testing with mocked dependencies.
*
* @return \Pterodactyl\Http\Controllers\Base\SecurityController
*/
private function getController(): SecurityController
{
return new SecurityController(
$this->alert,
$this->config,
$this->repository,
$this->toggleTwoFactorService,
$this->twoFactorSetupService
);
}
}

View File

@ -64,7 +64,7 @@ class RunTaskJobTest extends TestCase
{
parent::setUp();
Bus::fake();
Carbon::setTestNow();
Carbon::setTestNow(Carbon::now());
$this->commandRepository = m::mock(CommandRepositoryInterface::class);
$this->config = m::mock(Repository::class);

View File

@ -44,7 +44,7 @@ class DaemonKeyProviderServiceTest extends TestCase
public function setUp()
{
parent::setUp();
Carbon::setTestNow();
Carbon::setTestNow(Carbon::now());
$this->keyCreationService = m::mock(DaemonKeyCreationService::class);
$this->keyUpdateService = m::mock(DaemonKeyUpdateService::class);

View File

@ -107,6 +107,7 @@ class StartupModificationServiceTest extends TestCase
{
$model = factory(Server::class)->make([
'egg_id' => 123,
'image' => 'docker:image',
]);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
@ -121,22 +122,29 @@ class StartupModificationServiceTest extends TestCase
'variable_id' => 1,
], ['variable_value' => 'stored-value'])->once()->andReturnNull();
$this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']);
$this->repository->shouldReceive('update')->with($model->id, m::subset([
'installed' => 0,
'egg_id' => 456,
'pack_id' => 789,
'image' => 'docker:image',
]))->once()->andReturn($model);
$this->repository->shouldReceive('withColumns->getDaemonServiceData')->with($model->id)->once()->andReturn([]);
$this->repository->shouldReceive('getDaemonServiceData')->with($model, true)->once()->andReturn([
'egg' => 'abcd1234',
'pack' => 'xyz987',
]);
$this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']);
$this->daemonServerRepository->shouldReceive('setNode')->with($model->node_id)->once()->andReturnSelf();
$this->daemonServerRepository->shouldReceive('setAccessServer')->with($model->uuid)->once()->andReturnSelf();
$this->daemonServerRepository->shouldReceive('update')->with([
'build' => [
'env|overwrite' => ['env'],
'image' => $model->image,
],
'service' => [
'egg' => 'abcd1234',
'pack' => 'xyz987',
'skip_scripts' => false,
],
])->once()->andReturnSelf();
@ -145,7 +153,7 @@ class StartupModificationServiceTest extends TestCase
$service = $this->getService();
$service->setUserLevel(User::USER_LEVEL_ADMIN);
$service->handle($model, ['egg_id' => 456, 'pack_id' => 789, 'environment' => ['test' => 'abcd1234']]);
$service->handle($model, ['docker_image' => 'docker:image', 'egg_id' => 456, 'pack_id' => 789, 'environment' => ['test' => 'abcd1234']]);
$this->assertTrue(true);
}

View File

@ -1,37 +1,42 @@
<?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 Tests\Unit\Services\Users;
use Mockery as m;
use Carbon\Carbon;
use Tests\TestCase;
use Pterodactyl\Models\User;
use PragmaRX\Google2FA\Contracts\Google2FA;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Services\Users\ToggleTwoFactorService;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
class ToggleTwoFactorServiceTest extends TestCase
{
/**
* @var \PragmaRX\Google2FA\Contracts\Google2FA
*/
protected $google2FA;
const TEST_WINDOW_INT = 4;
const USER_TOTP_SECRET = 'encryptedValue';
const DECRYPTED_USER_SECRET = 'decryptedValue';
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
protected $repository;
private $config;
/**
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
protected $service;
private $encrypter;
/**
* @var \PragmaRX\Google2FA\Google2FA|\Mockery\Mock
*/
private $google2FA;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
@ -39,11 +44,15 @@ class ToggleTwoFactorServiceTest extends TestCase
public function setUp()
{
parent::setUp();
Carbon::setTestNow(Carbon::now());
$this->config = m::mock(Repository::class);
$this->encrypter = m::mock(Encrypter::class);
$this->google2FA = m::mock(Google2FA::class);
$this->repository = m::mock(UserRepositoryInterface::class);
$this->service = new ToggleTwoFactorService($this->google2FA, $this->repository);
$this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.window')->once()->andReturn(self::TEST_WINDOW_INT);
$this->encrypter->shouldReceive('decrypt')->with(self::USER_TOTP_SECRET)->once()->andReturn(self::DECRYPTED_USER_SECRET);
}
/**
@ -51,13 +60,15 @@ class ToggleTwoFactorServiceTest extends TestCase
*/
public function testTwoFactorIsEnabledForUser()
{
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => false]);
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => false]);
$this->google2FA->shouldReceive('verifyKey')->with($model->totp_secret, 'test-token', 2)->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($model->id, ['use_totp' => true])->once()->andReturnNull();
$this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, [
'totp_authenticated_at' => Carbon::now(),
'use_totp' => true,
])->once()->andReturnNull();
$this->assertTrue($this->service->handle($model, 'test-token'));
$this->assertTrue($this->getService()->handle($model, 'test-token'));
}
/**
@ -65,13 +76,15 @@ class ToggleTwoFactorServiceTest extends TestCase
*/
public function testTwoFactorIsDisabled()
{
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => true]);
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => true]);
$this->google2FA->shouldReceive('verifyKey')->with($model->totp_secret, 'test-token', 2)->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($model->id, ['use_totp' => false])->once()->andReturnNull();
$this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, [
'totp_authenticated_at' => Carbon::now(),
'use_totp' => false,
])->once()->andReturnNull();
$this->assertTrue($this->service->handle($model, 'test-token'));
$this->assertTrue($this->getService()->handle($model, 'test-token'));
}
/**
@ -79,13 +92,15 @@ class ToggleTwoFactorServiceTest extends TestCase
*/
public function testTwoFactorRemainsDisabledForUser()
{
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => false]);
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => false]);
$this->google2FA->shouldReceive('verifyKey')->with($model->totp_secret, 'test-token', 2)->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($model->id, ['use_totp' => false])->once()->andReturnNull();
$this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, [
'totp_authenticated_at' => Carbon::now(),
'use_totp' => false,
])->once()->andReturnNull();
$this->assertTrue($this->service->handle($model, 'test-token', false));
$this->assertTrue($this->getService()->handle($model, 'test-token', false));
}
/**
@ -95,23 +110,19 @@ class ToggleTwoFactorServiceTest extends TestCase
*/
public function testExceptionIsThrownIfTokenIsInvalid()
{
$model = factory(User::class)->make();
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET]);
$this->google2FA->shouldReceive('verifyKey')->once()->andReturn(false);
$this->service->handle($model, 'test-token');
$this->getService()->handle($model, 'test-token');
}
/**
* Test that an integer can be passed in place of a user model.
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\Users\ToggleTwoFactorService
*/
public function testIntegerCanBePassedInPlaceOfUserModel()
private function getService(): ToggleTwoFactorService
{
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => false]);
$this->repository->shouldReceive('find')->with($model->id)->once()->andReturn($model);
$this->google2FA->shouldReceive('verifyKey')->once()->andReturn(true);
$this->repository->shouldReceive('withoutFresh->update')->once()->andReturnNull();
$this->assertTrue($this->service->handle($model->id, 'test-token'));
return new ToggleTwoFactorService($this->encrypter, $this->google2FA, $this->config, $this->repository);
}
}

View File

@ -1,43 +1,37 @@
<?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 Tests\Unit\Services\Users;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\User;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Contracts\Config\Repository;
use PragmaRX\Google2FA\Contracts\Google2FA;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Services\Users\TwoFactorSetupService;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
class TwoFactorSetupServiceTest extends TestCase
{
/**
* @var \Illuminate\Contracts\Config\Repository
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
protected $config;
private $config;
/**
* @var \PragmaRX\Google2FA\Contracts\Google2FA
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
protected $google2FA;
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
* @var \PragmaRX\Google2FA\Google2FA|\Mockery\Mock
*/
protected $repository;
private $google2FA;
/**
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
*/
protected $service;
private $repository;
/**
* Setup tests.
@ -47,10 +41,9 @@ class TwoFactorSetupServiceTest extends TestCase
parent::setUp();
$this->config = m::mock(Repository::class);
$this->encrypter = m::mock(Encrypter::class);
$this->google2FA = m::mock(Google2FA::class);
$this->repository = m::mock(UserRepositoryInterface::class);
$this->service = new TwoFactorSetupService($this->config, $this->google2FA, $this->repository);
}
/**
@ -60,34 +53,25 @@ class TwoFactorSetupServiceTest extends TestCase
{
$model = factory(User::class)->make();
$this->google2FA->shouldReceive('generateSecretKey')->withNoArgs()->once()->andReturn('secretKey');
$this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.bytes')->once()->andReturn(32);
$this->google2FA->shouldReceive('generateSecretKey')->with(32)->once()->andReturn('secretKey');
$this->config->shouldReceive('get')->with('app.name')->once()->andReturn('CompanyName');
$this->google2FA->shouldReceive('getQRCodeGoogleUrl')->with('CompanyName', $model->email, 'secretKey')
->once()->andReturn('http://url.com');
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('update')->with($model->id, ['totp_secret' => 'secretKey'])->once()->andReturnNull();
$this->google2FA->shouldReceive('getQRCodeGoogleUrl')->with('CompanyName', $model->email, 'secretKey')->once()->andReturn('http://url.com');
$this->encrypter->shouldReceive('encrypt')->with('secretKey')->once()->andReturn('encryptedSecret');
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, ['totp_secret' => 'encryptedSecret'])->once()->andReturnNull();
$response = $this->service->handle($model);
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
$this->assertArrayHasKey('qrImage', $response);
$this->assertArrayHasKey('secret', $response);
$this->assertEquals('http://url.com', $response['qrImage']);
$this->assertEquals('secretKey', $response['secret']);
$this->assertSame('http://url.com', $response);
}
/**
* Test that an integer can be passed in place of the user model.
* Return an instance of the service to test with mocked dependencies.
*
* @return \Pterodactyl\Services\Users\TwoFactorSetupService
*/
public function testIntegerCanBePassedInPlaceOfUserModel()
private function getService(): TwoFactorSetupService
{
$model = factory(User::class)->make();
$this->repository->shouldReceive('find')->with($model->id)->once()->andReturn($model);
$this->google2FA->shouldReceive('generateSecretKey')->withNoArgs()->once()->andReturnNull();
$this->config->shouldReceive('get')->with('app.name')->once()->andReturnNull();
$this->google2FA->shouldReceive('getQRCodeGoogleUrl')->once()->andReturnNull();
$this->repository->shouldReceive('withoutFresh->update')->once()->andReturnNull();
$this->assertTrue(is_array($this->service->handle($model->id)));
return new TwoFactorSetupService($this->config, $this->encrypter, $this->google2FA, $this->repository);
}
}