forked from Alex/Pterodactyl-Panel
Merge branch 'develop' into develop
This commit is contained in:
commit
9a588cb6dc
@ -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
|
||||
|
@ -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]));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
@ -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',
|
||||
];
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Pterodactyl\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Pterodactyl\Providers;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Repository;
|
||||
namespace Pterodactyl\Repositories;
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Pterodactyl\Contracts\Repository\RepositoryInterface;
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
|
@ -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'])]
|
||||
);
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
]);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -10,7 +10,6 @@
|
||||
namespace Pterodactyl\Transformers\User;
|
||||
|
||||
use Pterodactyl\Models\Subuser;
|
||||
use Pterodactyl\Models\Permission;
|
||||
use League\Fractal\TransformerAbstract;
|
||||
|
||||
class SubuserTransformer extends TransformerAbstract
|
||||
|
@ -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
189
composer.lock
generated
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -23,6 +23,11 @@ return [
|
||||
*/
|
||||
'auth' => [
|
||||
'notifications' => env('LOGIN_NOTIFICATIONS', false),
|
||||
'2fa' => [
|
||||
'bytes' => 32,
|
||||
'window' => env('APP_2FA_WINDOW', 4),
|
||||
'verify_newer' => true,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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.');
|
||||
|
@ -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.',
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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ñol</option>
|
||||
<option value="fr" @if(Settings::get('default_language') === 'fr')selected @endif>Franç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ês</option>
|
||||
<option value="ru" @if(Settings::get('default_language') === 'ru')selected @endif>русский</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>中国的的</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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">«</span></li>
|
||||
@else
|
||||
<li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">«</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">»</a></li>
|
||||
@else
|
||||
<li class="page-item disabled"><span class="page-link">»</span></li>
|
||||
@endif
|
||||
</ul>
|
||||
@endif
|
@ -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>«</span></li> --}}
|
||||
|
@ -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">«</span></li>
|
||||
@else
|
||||
<li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a></li>
|
||||
@endif
|
||||
|
||||
<!-- Next Page Link -->
|
||||
@if ($paginator->hasMorePages())
|
||||
<li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">»</a></li>
|
||||
@else
|
||||
<li class="page-item disabled"><span class="page-link">»</span></li>
|
||||
@endif
|
||||
</ul>
|
||||
@endif
|
@ -1,17 +0,0 @@
|
||||
@if ($paginator->count() > 1)
|
||||
<ul class="pagination">
|
||||
<!-- Previous Page Link -->
|
||||
@if ($paginator->onFirstPage())
|
||||
<li class="disabled"><span>«</span></li>
|
||||
@else
|
||||
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a></li>
|
||||
@endif
|
||||
|
||||
<!-- Next Page Link -->
|
||||
@if ($paginator->hasMorePages())
|
||||
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">»</a></li>
|
||||
@else
|
||||
<li class="disabled"><span>»</span></li>
|
||||
@endif
|
||||
</ul>
|
||||
@endif
|
@ -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');
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user