Should wrap up the base landing page stuff for accounts, next step is server rendering
This commit is contained in:
parent
67ac36f5ce
commit
e045ef443a
32 changed files with 1223 additions and 317 deletions
4
.php_cs
4
.php_cs
|
@ -25,6 +25,10 @@ return PhpCsFixer\Config::create()
|
||||||
'declare_equal_normalize' => ['space' => 'single'],
|
'declare_equal_normalize' => ['space' => 'single'],
|
||||||
'heredoc_to_nowdoc' => true,
|
'heredoc_to_nowdoc' => true,
|
||||||
'linebreak_after_opening_tag' => true,
|
'linebreak_after_opening_tag' => true,
|
||||||
|
'method_argument_space' => [
|
||||||
|
'ensure_fully_multiline' => false,
|
||||||
|
'keep_multiple_spaces_after_comma' => false,
|
||||||
|
],
|
||||||
'new_with_braces' => false,
|
'new_with_braces' => false,
|
||||||
'no_alias_functions' => true,
|
'no_alias_functions' => true,
|
||||||
'no_multiline_whitespace_before_semicolons' => true,
|
'no_multiline_whitespace_before_semicolons' => true,
|
||||||
|
|
|
@ -88,4 +88,11 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
* @return \Psr\Http\Message\ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function delete();
|
public function delete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return detials on a specific server.
|
||||||
|
*
|
||||||
|
* @return \Psr\Http\Message\ResponseInterface
|
||||||
|
*/
|
||||||
|
public function details();
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,12 @@ interface RepositoryInterface
|
||||||
*
|
*
|
||||||
* @param array $fields
|
* @param array $fields
|
||||||
* @param bool $validate
|
* @param bool $validate
|
||||||
|
* @param bool $force
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
*/
|
*/
|
||||||
public function create(array $fields, $validate = true);
|
public function create(array $fields, $validate = true, $force = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a given record from the database.
|
* Delete a given record from the database.
|
||||||
|
|
|
@ -87,4 +87,33 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
*/
|
*/
|
||||||
public function getDaemonServiceData($id);
|
public function getDaemonServiceData($id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array of server IDs that a given user can access based on owner and subuser permissions.
|
||||||
|
*
|
||||||
|
* @param int $user
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getUserAccessServers($user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a paginated list of servers that a user can access at a given level.
|
||||||
|
*
|
||||||
|
* @param int $user
|
||||||
|
* @param string $level
|
||||||
|
* @param bool $admin
|
||||||
|
* @param array $relations
|
||||||
|
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||||
|
*/
|
||||||
|
public function filterUserAccessServers($user, $admin = false, $level = 'all', array $relations = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a server by UUID.
|
||||||
|
*
|
||||||
|
* @param string $uuid
|
||||||
|
* @return \Illuminate\Database\Eloquent\Collection
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function getByUuid($uuid);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/*
|
||||||
* Pterodactyl - Panel
|
* Pterodactyl - Panel
|
||||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
*
|
*
|
||||||
|
@ -22,50 +22,24 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Base;
|
namespace Pterodactyl\Contracts\Repository;
|
||||||
|
|
||||||
use Auth;
|
interface SessionRepositoryInterface extends RepositoryInterface
|
||||||
use Session;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
|
||||||
|
|
||||||
class LanguageController extends Controller
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* A list of supported languages on the panel.
|
* Delete a session for a given user.
|
||||||
*
|
*
|
||||||
* @var array
|
* @param int $user
|
||||||
|
* @param int $session
|
||||||
|
* @return null|int
|
||||||
*/
|
*/
|
||||||
protected $languages = [
|
public function deleteUserSession($user, $session);
|
||||||
'de' => 'German',
|
|
||||||
'en' => 'English',
|
|
||||||
'et' => 'Estonian',
|
|
||||||
'nb' => 'Norwegian',
|
|
||||||
'nl' => 'Dutch',
|
|
||||||
'pt' => 'Portuguese',
|
|
||||||
'ro' => 'Romanian',
|
|
||||||
'ru' => 'Russian',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the language for a user.
|
* Return all of the active sessions for a user.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param int $user
|
||||||
* @param string $language
|
* @return \Illuminate\Support\Collection
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
|
||||||
*/
|
*/
|
||||||
public function setLanguage(Request $request, $language)
|
public function getUserSessions($user);
|
||||||
{
|
|
||||||
if (array_key_exists($language, $this->languages)) {
|
|
||||||
if (Auth::check()) {
|
|
||||||
$user = User::findOrFail(Auth::user()->id);
|
|
||||||
$user->language = $language;
|
|
||||||
$user->save();
|
|
||||||
}
|
|
||||||
Session::put('applocale', $language);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Exceptions\Http\Base;
|
||||||
|
|
||||||
|
use Pterodactyl\Exceptions\DisplayException;
|
||||||
|
|
||||||
|
class InvalidPasswordProvidedException extends DisplayException
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Exceptions\Service\User;
|
||||||
|
|
||||||
|
class TwoFactorAuthenticationTokenInvalid extends \Exception
|
||||||
|
{
|
||||||
|
}
|
|
@ -27,12 +27,11 @@ namespace Pterodactyl\Http\Controllers\Base;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Prologue\Alerts\AlertsMessageBag;
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
|
use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest;
|
||||||
use Pterodactyl\Models\APIPermission;
|
use Pterodactyl\Models\APIPermission;
|
||||||
use Pterodactyl\Services\ApiKeyService;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Http\Requests\ApiKeyRequest;
|
|
||||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
|
||||||
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
||||||
|
use Pterodactyl\Services\Api\KeyCreationService;
|
||||||
|
|
||||||
class APIController extends Controller
|
class APIController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -41,31 +40,31 @@ class APIController extends Controller
|
||||||
*/
|
*/
|
||||||
protected $alert;
|
protected $alert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Api\KeyCreationService
|
||||||
|
*/
|
||||||
|
protected $keyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
|
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
|
||||||
*/
|
*/
|
||||||
protected $repository;
|
protected $repository;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Pterodactyl\Services\ApiKeyService
|
|
||||||
*/
|
|
||||||
protected $service;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIController constructor.
|
* APIController constructor.
|
||||||
*
|
*
|
||||||
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
||||||
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
|
||||||
* @param \Pterodactyl\Services\ApiKeyService $service
|
* @param \Pterodactyl\Services\Api\KeyCreationService $keyService
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AlertsMessageBag $alert,
|
AlertsMessageBag $alert,
|
||||||
ApiKeyRepositoryInterface $repository,
|
ApiKeyRepositoryInterface $repository,
|
||||||
ApiKeyService $service
|
KeyCreationService $keyService
|
||||||
) {
|
) {
|
||||||
$this->alert = $alert;
|
$this->alert = $alert;
|
||||||
|
$this->keyService = $keyService;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->service = $service;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,6 +72,8 @@ class APIController extends Controller
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @return \Illuminate\View\View
|
* @return \Illuminate\View\View
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -84,14 +85,15 @@ class APIController extends Controller
|
||||||
/**
|
/**
|
||||||
* Display API key creation page.
|
* Display API key creation page.
|
||||||
*
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
* @return \Illuminate\View\View
|
* @return \Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function create()
|
public function create(Request $request)
|
||||||
{
|
{
|
||||||
return view('base.api.new', [
|
return view('base.api.new', [
|
||||||
'permissions' => [
|
'permissions' => [
|
||||||
'user' => collect(APIPermission::CONST_PERMISSIONS)->pull('_user'),
|
'user' => collect(APIPermission::CONST_PERMISSIONS)->pull('_user'),
|
||||||
'admin' => collect(APIPermission::CONST_PERMISSIONS)->except('_user')->toArray(),
|
'admin' => ! $request->user()->root_admin ?: collect(APIPermission::CONST_PERMISSIONS)->except('_user')->toArray(),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -99,30 +101,25 @@ class APIController extends Controller
|
||||||
/**
|
/**
|
||||||
* Handle saving new API key.
|
* Handle saving new API key.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Http\Requests\ApiKeyRequest $request
|
* @param \Pterodactyl\Http\Requests\Base\ApiKeyFormRequest $request
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
*
|
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
*/
|
*/
|
||||||
public function store(ApiKeyRequest $request)
|
public function store(ApiKeyFormRequest $request)
|
||||||
{
|
{
|
||||||
$adminPermissions = [];
|
$adminPermissions = [];
|
||||||
if ($request->user()->isRootAdmin()) {
|
if ($request->user()->root_admin) {
|
||||||
$adminPermissions = $request->input('admin_permissions') ?? [];
|
$adminPermissions = $request->input('admin_permissions') ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$secret = $this->service->create([
|
$secret = $this->keyService->handle([
|
||||||
'user_id' => $request->user()->id,
|
'user_id' => $request->user()->id,
|
||||||
'allowed_ips' => $request->input('allowed_ips'),
|
'allowed_ips' => $request->input('allowed_ips'),
|
||||||
'memo' => $request->input('memo'),
|
'memo' => $request->input('memo'),
|
||||||
], $request->input('permissions') ?? [], $adminPermissions);
|
], $request->input('permissions', []), $adminPermissions);
|
||||||
|
|
||||||
$this->alert->success(
|
$this->alert->success(trans('base.api.index.keypair_created', ['token' => $secret]))->flash();
|
||||||
"An API Key-Pair has successfully been generated. The API secret
|
|
||||||
for this public key is shown below and will not be shown again.
|
|
||||||
<br /><br /><code>{$secret}</code>"
|
|
||||||
)->flash();
|
|
||||||
|
|
||||||
return redirect()->route('account.api');
|
return redirect()->route('account.api');
|
||||||
}
|
}
|
||||||
|
@ -136,16 +133,10 @@ class APIController extends Controller
|
||||||
*/
|
*/
|
||||||
public function revoke(Request $request, $key)
|
public function revoke(Request $request, $key)
|
||||||
{
|
{
|
||||||
try {
|
$this->repository->deleteWhere([
|
||||||
$key = $this->repository->withColumns('id')->findFirstWhere([
|
['user_id', '=', $request->user()->id],
|
||||||
['user_id', '=', $request->user()->id],
|
['public', '=', $key],
|
||||||
['public', $key],
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
$this->service->revoke($key->id);
|
|
||||||
} catch (RecordNotFoundException $ex) {
|
|
||||||
return abort(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response('', 204);
|
return response('', 204);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,83 +25,69 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Base;
|
namespace Pterodactyl\Http\Controllers\Base;
|
||||||
|
|
||||||
use Log;
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
use Alert;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Pterodactyl\Models\User;
|
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
use Pterodactyl\Exceptions\DisplayValidationException;
|
use Pterodactyl\Http\Requests\Base\AccountDataFormRequest;
|
||||||
|
use Pterodactyl\Services\Users\UserUpdateService;
|
||||||
|
|
||||||
class AccountController extends Controller
|
class AccountController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct()
|
/**
|
||||||
{
|
* @var \Prologue\Alerts\AlertsMessageBag
|
||||||
|
*/
|
||||||
|
protected $alert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Users\UserUpdateService
|
||||||
|
*/
|
||||||
|
protected $updateService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AccountController constructor.
|
||||||
|
*
|
||||||
|
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
||||||
|
* @param \Pterodactyl\Services\Users\UserUpdateService $updateService
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
AlertsMessageBag $alert,
|
||||||
|
UserUpdateService $updateService
|
||||||
|
) {
|
||||||
|
$this->alert = $alert;
|
||||||
|
$this->updateService = $updateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display base account information page.
|
* Display base account information page.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\View\View
|
* @return \Illuminate\View\View
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index()
|
||||||
{
|
{
|
||||||
return view('base.account');
|
return view('base.account');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update details for a users account.
|
* Update details for a user's account.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Pterodactyl\Http\Requests\Base\AccountDataFormRequest $request
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
*/
|
*/
|
||||||
public function update(Request $request)
|
public function update(AccountDataFormRequest $request)
|
||||||
{
|
{
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
// Request to update account Password
|
|
||||||
if ($request->input('do_action') === 'password') {
|
if ($request->input('do_action') === 'password') {
|
||||||
$this->validate($request, [
|
|
||||||
'current_password' => 'required',
|
|
||||||
'new_password' => 'required|confirmed|' . User::PASSWORD_RULES,
|
|
||||||
'new_password_confirmation' => 'required',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$data['password'] = $request->input('new_password');
|
$data['password'] = $request->input('new_password');
|
||||||
|
|
||||||
// Request to update account Email
|
|
||||||
} elseif ($request->input('do_action') === 'email') {
|
} elseif ($request->input('do_action') === 'email') {
|
||||||
$data['email'] = $request->input('new_email');
|
$data['email'] = $request->input('new_email');
|
||||||
|
|
||||||
// Request to update account Identity
|
|
||||||
} elseif ($request->input('do_action') === 'identity') {
|
} elseif ($request->input('do_action') === 'identity') {
|
||||||
$data = $request->only(['name_first', 'name_last', 'username']);
|
$data = $request->only(['name_first', 'name_last', 'username']);
|
||||||
|
|
||||||
// Unknown, hit em with a 404
|
|
||||||
} else {
|
|
||||||
return abort(404);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
$this->updateService->handle($request->user()->id, $data);
|
||||||
in_array($request->input('do_action'), ['email', 'password'])
|
$this->alert->success(trans('base.account.details_updated'))->flash();
|
||||||
&& ! password_verify($request->input('current_password'), $request->user()->password)
|
|
||||||
) {
|
|
||||||
Alert::danger(trans('base.account.invalid_pass'))->flash();
|
|
||||||
|
|
||||||
return redirect()->route('account');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$repo = new oldUserRepository;
|
|
||||||
$repo->update($request->user()->id, $data);
|
|
||||||
Alert::success('Your account details were successfully updated.')->flash();
|
|
||||||
} catch (DisplayValidationException $ex) {
|
|
||||||
return redirect()->route('account')->withErrors(json_decode($ex->getMessage()));
|
|
||||||
} catch (\Exception $ex) {
|
|
||||||
Log::error($ex);
|
|
||||||
Alert::danger(trans('base.account.exception'))->flash();
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->route('account');
|
return redirect()->route('account');
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,45 @@
|
||||||
namespace Pterodactyl\Http\Controllers\Base;
|
namespace Pterodactyl\Http\Controllers\Base;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
|
use Pterodactyl\Services\Servers\ServerAccessHelperService;
|
||||||
|
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
|
||||||
|
|
||||||
class IndexController extends Controller
|
class IndexController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Servers\ServerAccessHelperService
|
||||||
|
*/
|
||||||
|
protected $access;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $daemonRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IndexController constructor.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
|
||||||
|
* @param \Pterodactyl\Services\Servers\ServerAccessHelperService $access
|
||||||
|
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
DaemonServerRepositoryInterface $daemonRepository,
|
||||||
|
ServerAccessHelperService $access,
|
||||||
|
ServerRepositoryInterface $repository
|
||||||
|
) {
|
||||||
|
$this->access = $access;
|
||||||
|
$this->daemonRepository = $daemonRepository;
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns listing of user's servers.
|
* Returns listing of user's servers.
|
||||||
*
|
*
|
||||||
|
@ -39,38 +73,11 @@ class IndexController extends Controller
|
||||||
*/
|
*/
|
||||||
public function getIndex(Request $request)
|
public function getIndex(Request $request)
|
||||||
{
|
{
|
||||||
$servers = $request->user()->access()->with('user');
|
$servers = $this->repository->search($request->input('query'))->filterUserAccessServers(
|
||||||
|
$request->user()->id, $request->user()->root_admin, 'all', ['user']
|
||||||
|
);
|
||||||
|
|
||||||
if (! is_null($request->input('query'))) {
|
return view('base.index', ['servers' => $servers]);
|
||||||
$servers->search($request->input('query'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('base.index', [
|
|
||||||
'servers' => $servers->paginate(config('pterodactyl.paginate.frontend.servers')),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random string.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param int $length
|
|
||||||
* @return string
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
public function getPassword(Request $request, $length = 16)
|
|
||||||
{
|
|
||||||
$length = ($length < 8) ? 8 : $length;
|
|
||||||
|
|
||||||
$returnable = false;
|
|
||||||
while (! $returnable) {
|
|
||||||
$generated = str_random($length);
|
|
||||||
if (preg_match('/[A-Z]+[a-z]+[0-9]+/', $generated)) {
|
|
||||||
$returnable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $generated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,31 +86,23 @@ class IndexController extends Controller
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @param string $uuid
|
* @param string $uuid
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function status(Request $request, $uuid)
|
public function status(Request $request, $uuid)
|
||||||
{
|
{
|
||||||
$server = Server::byUuid($uuid);
|
$server = $this->access->handle($uuid, $request->user());
|
||||||
|
|
||||||
if (! $server) {
|
|
||||||
return response()->json([], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $server->installed) {
|
if (! $server->installed) {
|
||||||
return response()->json(['status' => 20]);
|
return response()->json(['status' => 20]);
|
||||||
}
|
} elseif ($server->suspended) {
|
||||||
|
|
||||||
if ($server->suspended) {
|
|
||||||
return response()->json(['status' => 30]);
|
return response()->json(['status' => 30]);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
$response = $this->daemonRepository->setNode($server->node_id)
|
||||||
$res = $server->guzzleClient()->request('GET', '/server');
|
->setAccessServer($server->uuid)
|
||||||
if ($res->getStatusCode() === 200) {
|
->setAccessToken($server->daemonSecret)
|
||||||
return response()->json(json_decode($res->getBody()));
|
->details();
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([]);
|
return response()->json(json_decode($response->getBody()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,64 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Base;
|
namespace Pterodactyl\Http\Controllers\Base;
|
||||||
|
|
||||||
use Alert;
|
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||||
use Google2FA;
|
use Illuminate\Contracts\Session\Session;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Pterodactyl\Models\Session;
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
|
use Pterodactyl\Contracts\Repository\SessionRepositoryInterface;
|
||||||
|
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
|
||||||
use Pterodactyl\Http\Controllers\Controller;
|
use Pterodactyl\Http\Controllers\Controller;
|
||||||
|
use Pterodactyl\Services\Users\ToggleTwoFactorService;
|
||||||
|
use Pterodactyl\Services\Users\TwoFactorSetupService;
|
||||||
|
|
||||||
class SecurityController extends Controller
|
class SecurityController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var \Prologue\Alerts\AlertsMessageBag
|
||||||
|
*/
|
||||||
|
protected $alert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Config\Repository
|
||||||
|
*/
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Session\Session
|
||||||
|
*/
|
||||||
|
protected $session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
|
||||||
|
*/
|
||||||
|
protected $toggleTwoFactorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
|
||||||
|
*/
|
||||||
|
protected $twoFactorSetupService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AlertsMessageBag $alert,
|
||||||
|
ConfigRepository $config,
|
||||||
|
Session $session,
|
||||||
|
SessionRepositoryInterface $repository,
|
||||||
|
ToggleTwoFactorService $toggleTwoFactorService,
|
||||||
|
TwoFactorSetupService $twoFactorSetupService
|
||||||
|
) {
|
||||||
|
$this->alert = $alert;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->repository = $repository;
|
||||||
|
$this->session = $session;
|
||||||
|
$this->toggleTwoFactorService = $toggleTwoFactorService;
|
||||||
|
$this->twoFactorSetupService = $twoFactorSetupService;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns Security Management Page.
|
* Returns Security Management Page.
|
||||||
*
|
*
|
||||||
|
@ -41,8 +91,12 @@ class SecurityController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
|
if ($this->config->get('session.driver') === 'database') {
|
||||||
|
$activeSessions = $this->repository->getUserSessions($request->user()->id);
|
||||||
|
}
|
||||||
|
|
||||||
return view('base.security', [
|
return view('base.security', [
|
||||||
'sessions' => Session::where('user_id', $request->user()->id)->get(),
|
'sessions' => $activeSessions ?? null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,22 +106,13 @@ class SecurityController extends Controller
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @return \Illuminate\Http\JsonResponse
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
*/
|
*/
|
||||||
public function generateTotp(Request $request)
|
public function generateTotp(Request $request)
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
return response()->json($this->twoFactorSetupService->handle($request->user()));
|
||||||
|
|
||||||
$user->totp_secret = Google2FA::generateSecretKey();
|
|
||||||
$user->save();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'qrImage' => Google2FA::getQRCodeGoogleUrl(
|
|
||||||
'Pterodactyl',
|
|
||||||
$user->email,
|
|
||||||
$user->totp_secret
|
|
||||||
),
|
|
||||||
'secret' => $user->totp_secret,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,18 +123,13 @@ class SecurityController extends Controller
|
||||||
*/
|
*/
|
||||||
public function setTotp(Request $request)
|
public function setTotp(Request $request)
|
||||||
{
|
{
|
||||||
if (! $request->has('token')) {
|
try {
|
||||||
return response()->json([
|
$this->toggleTwoFactorService->handle($request->user(), $request->input('token'));
|
||||||
'error' => 'Request is missing token parameter.',
|
|
||||||
], 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $request->user();
|
|
||||||
if ($user->toggleTotp($request->input('token'))) {
|
|
||||||
return response('true');
|
return response('true');
|
||||||
|
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
|
||||||
|
return response('false');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response('false');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,19 +140,12 @@ class SecurityController extends Controller
|
||||||
*/
|
*/
|
||||||
public function disableTotp(Request $request)
|
public function disableTotp(Request $request)
|
||||||
{
|
{
|
||||||
if (! $request->has('token')) {
|
try {
|
||||||
Alert::danger('Missing required `token` field in request.')->flash();
|
$this->toggleTwoFactorService->handle($request->user(), $request->input('token'), false);
|
||||||
|
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
|
||||||
return redirect()->route('account.security');
|
$this->alert->danger(trans('base.security.2fa_disable_error'))->flash();
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $request->user();
|
|
||||||
if ($user->toggleTotp($request->input('token'))) {
|
|
||||||
return redirect()->route('account.security');
|
|
||||||
}
|
|
||||||
|
|
||||||
Alert::danger('The TOTP token provided was invalid.')->flash();
|
|
||||||
|
|
||||||
return redirect()->route('account.security');
|
return redirect()->route('account.security');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +158,7 @@ class SecurityController extends Controller
|
||||||
*/
|
*/
|
||||||
public function revoke(Request $request, $id)
|
public function revoke(Request $request, $id)
|
||||||
{
|
{
|
||||||
Session::where('user_id', $request->user()->id)->findOrFail($id)->delete();
|
$this->repository->deleteUserSession($request->user()->id, $id);
|
||||||
|
|
||||||
return redirect()->route('account.security');
|
return redirect()->route('account.security');
|
||||||
}
|
}
|
||||||
|
|
85
app/Http/Requests/Base/AccountDataFormRequest.php
Normal file
85
app/Http/Requests/Base/AccountDataFormRequest.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Base;
|
||||||
|
|
||||||
|
use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
|
||||||
|
|
||||||
|
class AccountDataFormRequest extends FrontendUserFormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
* @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
if (! parent::authorize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify password matches when changing password or email.
|
||||||
|
if (in_array($this->input('do_action'), ['password', 'email'])) {
|
||||||
|
if (! password_verify($this->input('current_password'), $this->user()->password)) {
|
||||||
|
throw new InvalidPasswordProvidedException(trans('base.account.invalid_password'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
$modelRules = User::getUpdateRulesForId($this->user()->id);
|
||||||
|
|
||||||
|
switch ($this->input('do_action')) {
|
||||||
|
case 'email':
|
||||||
|
$rules = [
|
||||||
|
'new_email' => array_get($modelRules, 'email'),
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case 'password':
|
||||||
|
$rules = [
|
||||||
|
'new_password' => 'required|confirmed|string|min:8',
|
||||||
|
'new_password_confirmation' => 'required',
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case 'identity':
|
||||||
|
$rules = [
|
||||||
|
'name_first' => array_get($modelRules, 'name_first'),
|
||||||
|
'name_last' => array_get($modelRules, 'name_last'),
|
||||||
|
'username' => array_get($modelRules, 'username'),
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
abort(422);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,11 +22,12 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Requests;
|
namespace Pterodactyl\Http\Requests\Base;
|
||||||
|
|
||||||
use IPTools\Network;
|
use IPTools\Network;
|
||||||
|
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
|
||||||
|
|
||||||
class ApiKeyRequest extends BaseFormRequest
|
class ApiKeyFormRequest extends FrontendUserFormRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Rules applied to data passed in this request.
|
* Rules applied to data passed in this request.
|
||||||
|
@ -58,7 +59,7 @@ class ApiKeyRequest extends BaseFormRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->merge(['allowed_ips' => $loop], $this->except('allowed_ips'));
|
$this->merge(['allowed_ips' => $loop]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,12 +70,11 @@ class ApiKeyRequest extends BaseFormRequest
|
||||||
public function withValidator($validator)
|
public function withValidator($validator)
|
||||||
{
|
{
|
||||||
$validator->after(function ($validator) {
|
$validator->after(function ($validator) {
|
||||||
|
/* @var \Illuminate\Validation\Validator $validator */
|
||||||
if (empty($this->input('permissions')) && empty($this->input('admin_permissions'))) {
|
if (empty($this->input('permissions')) && empty($this->input('admin_permissions'))) {
|
||||||
$validator->errors()->add('permissions', 'At least one permission must be selected.');
|
$validator->errors()->add('permissions', 'At least one permission must be selected.');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
$validator->after(function ($validator) {
|
|
||||||
foreach ($this->input('allowed_ips') as $ip) {
|
foreach ($this->input('allowed_ips') as $ip) {
|
||||||
$ip = trim($ip);
|
$ip = trim($ip);
|
||||||
|
|
|
@ -26,8 +26,10 @@ namespace Pterodactyl\Http\Requests;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class BaseFormRequest extends FormRequest
|
abstract class FrontendUserFormRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
abstract public function rules();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a user is authorized to access this endpoint.
|
* Determine if a user is authorized to access this endpoint.
|
||||||
*
|
*
|
|
@ -50,21 +50,6 @@ class User extends Model implements
|
||||||
{
|
{
|
||||||
use Authenticatable, Authorizable, CanResetPassword, Eloquence, Notifiable, Validable;
|
use Authenticatable, Authorizable, CanResetPassword, Eloquence, Notifiable, Validable;
|
||||||
|
|
||||||
/**
|
|
||||||
* The rules for user passwords.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The regex rules for usernames.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Level of servers to display when using access() on a user.
|
* Level of servers to display when using access() on a user.
|
||||||
*
|
*
|
||||||
|
@ -92,9 +77,9 @@ class User extends Model implements
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'root_admin' => 'integer',
|
'root_admin' => 'boolean',
|
||||||
'use_totp' => 'integer',
|
'use_totp' => 'boolean',
|
||||||
'gravatar' => 'integer',
|
'gravatar' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,11 +120,11 @@ class User extends Model implements
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected static $applicationRules = [
|
protected static $applicationRules = [
|
||||||
'email' => 'required|email',
|
'email' => 'required',
|
||||||
'username' => 'required|alpha_dash',
|
'username' => 'required',
|
||||||
'name_first' => 'required|string',
|
'name_first' => 'required',
|
||||||
'name_last' => 'required|string',
|
'name_last' => 'required',
|
||||||
'password' => 'sometimes|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
|
'password' => 'sometimes',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,10 +133,10 @@ class User extends Model implements
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected static $dataIntegrityRules = [
|
protected static $dataIntegrityRules = [
|
||||||
'email' => 'unique:users,email',
|
'email' => 'email|unique:users,email',
|
||||||
'username' => 'between:1,255|unique:users,username',
|
'username' => 'alpha_dash|between:1,255|unique:users,username',
|
||||||
'name_first' => 'between:1,255',
|
'name_first' => 'string|between:1,255',
|
||||||
'name_last' => 'between:1,255',
|
'name_last' => 'string|between:1,255',
|
||||||
'password' => 'nullable|string',
|
'password' => 'nullable|string',
|
||||||
'root_admin' => 'boolean',
|
'root_admin' => 'boolean',
|
||||||
'language' => 'string|between:2,5',
|
'language' => 'string|between:2,5',
|
||||||
|
|
|
@ -25,10 +25,16 @@
|
||||||
namespace Pterodactyl\Providers;
|
namespace Pterodactyl\Providers;
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface;
|
||||||
|
use Pterodactyl\Contracts\Repository\SessionRepositoryInterface;
|
||||||
|
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
|
||||||
use Pterodactyl\Repositories\Daemon\FileRepository;
|
use Pterodactyl\Repositories\Daemon\FileRepository;
|
||||||
use Pterodactyl\Repositories\Daemon\PowerRepository;
|
use Pterodactyl\Repositories\Daemon\PowerRepository;
|
||||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||||
use Pterodactyl\Repositories\Eloquent\PackRepository;
|
use Pterodactyl\Repositories\Eloquent\PackRepository;
|
||||||
|
use Pterodactyl\Repositories\Eloquent\PermissionRepository;
|
||||||
|
use Pterodactyl\Repositories\Eloquent\SessionRepository;
|
||||||
|
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
|
||||||
use Pterodactyl\Repositories\Eloquent\UserRepository;
|
use Pterodactyl\Repositories\Eloquent\UserRepository;
|
||||||
use Pterodactyl\Repositories\Daemon\CommandRepository;
|
use Pterodactyl\Repositories\Daemon\CommandRepository;
|
||||||
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
|
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
|
||||||
|
@ -83,11 +89,14 @@ class RepositoryServiceProvider extends ServiceProvider
|
||||||
$this->app->bind(NodeRepositoryInterface::class, NodeRepository::class);
|
$this->app->bind(NodeRepositoryInterface::class, NodeRepository::class);
|
||||||
$this->app->bind(OptionVariableRepositoryInterface::class, OptionVariableRepository::class);
|
$this->app->bind(OptionVariableRepositoryInterface::class, OptionVariableRepository::class);
|
||||||
$this->app->bind(PackRepositoryInterface::class, PackRepository::class);
|
$this->app->bind(PackRepositoryInterface::class, PackRepository::class);
|
||||||
|
$this->app->bind(PermissionRepositoryInterface::class, PermissionRepository::class);
|
||||||
$this->app->bind(ServerRepositoryInterface::class, ServerRepository::class);
|
$this->app->bind(ServerRepositoryInterface::class, ServerRepository::class);
|
||||||
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);
|
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);
|
||||||
$this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class);
|
$this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class);
|
||||||
$this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class);
|
$this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class);
|
||||||
$this->app->bind(ServiceVariableRepositoryInterface::class, ServiceVariableRepository::class);
|
$this->app->bind(ServiceVariableRepositoryInterface::class, ServiceVariableRepository::class);
|
||||||
|
$this->app->bind(SessionRepositoryInterface::class, SessionRepository::class);
|
||||||
|
$this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class);
|
||||||
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
|
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
|
||||||
|
|
||||||
// Daemon Repositories
|
// Daemon Repositories
|
||||||
|
|
|
@ -161,4 +161,12 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa
|
||||||
{
|
{
|
||||||
return $this->getHttpClient()->request('DELETE', '/servers');
|
return $this->getHttpClient()->request('DELETE', '/servers');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function details()
|
||||||
|
{
|
||||||
|
return $this->getHttpClient()->request('GET', '/servers');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ use Pterodactyl\Models\Server;
|
||||||
use Pterodactyl\Repositories\Concerns\Searchable;
|
use Pterodactyl\Repositories\Concerns\Searchable;
|
||||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
class ServerRepository extends EloquentRepository implements ServerRepositoryInterface
|
class ServerRepository extends EloquentRepository implements ServerRepositoryInterface
|
||||||
{
|
{
|
||||||
|
@ -149,4 +150,73 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
||||||
'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null,
|
'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getUserAccessServers($user)
|
||||||
|
{
|
||||||
|
Assert::numeric($user, 'First argument passed to getUserAccessServers must be numeric, received %s.');
|
||||||
|
|
||||||
|
$subuser = $this->app->make(SubuserRepository::class);
|
||||||
|
|
||||||
|
return $this->getBuilder()->select('id')->where('owner_id', $user)->union(
|
||||||
|
$subuser->getBuilder()->select('server_id')->where('user_id', $user)
|
||||||
|
)->pluck('id')->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function filterUserAccessServers($user, $admin = false, $level = 'all', array $relations = [])
|
||||||
|
{
|
||||||
|
Assert::numeric($user, 'First argument passed to filterUserAccessServers must be numeric, received %s.');
|
||||||
|
Assert::boolean($admin, 'Second argument passed to filterUserAccessServers must be boolean, received %s.');
|
||||||
|
Assert::stringNotEmpty($level, 'Third argument passed to filterUserAccessServers must be a non-empty string, received %s.');
|
||||||
|
|
||||||
|
$instance = $this->getBuilder()->with($relations);
|
||||||
|
|
||||||
|
// If access level is set to owner, only display servers
|
||||||
|
// that the user owns.
|
||||||
|
if ($level === 'owner') {
|
||||||
|
$instance->where('owner_id', $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If set to all, display all servers they can access, including
|
||||||
|
// those they access as an admin.
|
||||||
|
//
|
||||||
|
// If set to subuser, only return the servers they can access because
|
||||||
|
// they are owner, or marked as a subuser of the server.
|
||||||
|
if (($level === 'all' && ! $admin) || $level === 'subuser') {
|
||||||
|
$instance->whereIn('id', $this->getUserAccessServers($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If set to admin, only display the servers a user can access
|
||||||
|
// as an administrator (leaves out owned and subuser of).
|
||||||
|
if ($level === 'admin' && $admin) {
|
||||||
|
$instance->whereIn('id', $this->getUserAccessServers($user));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance->search($this->searchTerm)->paginate(
|
||||||
|
$this->app->make('config')->get('pterodactyl.paginate.frontend.servers')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getByUuid($uuid)
|
||||||
|
{
|
||||||
|
Assert::stringNotEmpty($uuid, 'First argument passed to getByUuid must be a non-empty string, received %s.');
|
||||||
|
|
||||||
|
$instance = $this->getBuilder()->with('service', 'node')->where(function ($query) use ($uuid) {
|
||||||
|
$query->where('uuidShort', $uuid)->orWhere('uuid', $uuid);
|
||||||
|
})->first($this->getColumns());
|
||||||
|
|
||||||
|
if (! $instance) {
|
||||||
|
throw new RecordNotFoundException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
55
app/Repositories/Eloquent/SessionRepository.php
Normal file
55
app/Repositories/Eloquent/SessionRepository.php
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Repositories\Eloquent;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Session;
|
||||||
|
use Pterodactyl\Contracts\Repository\SessionRepositoryInterface;
|
||||||
|
|
||||||
|
class SessionRepository extends EloquentRepository implements SessionRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function model()
|
||||||
|
{
|
||||||
|
return Session::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getUserSessions($user)
|
||||||
|
{
|
||||||
|
return $this->getBuilder()->where('user_id', $user)->get($this->getColumns());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function deleteUserSession($user, $session)
|
||||||
|
{
|
||||||
|
return $this->getBuilder()->where('user_id', $user)->where('id', $session)->delete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ use Illuminate\Database\ConnectionInterface;
|
||||||
use Illuminate\Contracts\Encryption\Encrypter;
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
||||||
|
|
||||||
class KeyService
|
class KeyCreationService
|
||||||
{
|
{
|
||||||
const PUB_CRYPTO_BYTES = 8;
|
const PUB_CRYPTO_BYTES = 8;
|
||||||
const PRIV_CRYPTO_BYTES = 32;
|
const PRIV_CRYPTO_BYTES = 32;
|
||||||
|
@ -36,7 +36,7 @@ class KeyService
|
||||||
/**
|
/**
|
||||||
* @var \Illuminate\Database\ConnectionInterface
|
* @var \Illuminate\Database\ConnectionInterface
|
||||||
*/
|
*/
|
||||||
protected $database;
|
protected $connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||||
|
@ -57,18 +57,18 @@ class KeyService
|
||||||
* ApiKeyService constructor.
|
* ApiKeyService constructor.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
|
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
|
||||||
* @param \Illuminate\Database\ConnectionInterface $database
|
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||||
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||||
* @param \Pterodactyl\Services\Api\PermissionService $permissionService
|
* @param \Pterodactyl\Services\Api\PermissionService $permissionService
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ApiKeyRepositoryInterface $repository,
|
ApiKeyRepositoryInterface $repository,
|
||||||
ConnectionInterface $database,
|
ConnectionInterface $connection,
|
||||||
Encrypter $encrypter,
|
Encrypter $encrypter,
|
||||||
PermissionService $permissionService
|
PermissionService $permissionService
|
||||||
) {
|
) {
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->database = $database;
|
$this->connection = $connection;
|
||||||
$this->encrypter = $encrypter;
|
$this->encrypter = $encrypter;
|
||||||
$this->permissionService = $permissionService;
|
$this->permissionService = $permissionService;
|
||||||
}
|
}
|
||||||
|
@ -84,13 +84,13 @@ class KeyService
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
*/
|
*/
|
||||||
public function create(array $data, array $permissions, array $administrative = [])
|
public function handle(array $data, array $permissions, array $administrative = [])
|
||||||
{
|
{
|
||||||
$publicKey = bin2hex(random_bytes(self::PUB_CRYPTO_BYTES));
|
$publicKey = bin2hex(random_bytes(self::PUB_CRYPTO_BYTES));
|
||||||
$secretKey = bin2hex(random_bytes(self::PRIV_CRYPTO_BYTES));
|
$secretKey = bin2hex(random_bytes(self::PRIV_CRYPTO_BYTES));
|
||||||
|
|
||||||
// Start a Transaction
|
// Start a Transaction
|
||||||
$this->database->beginTransaction();
|
$this->connection->beginTransaction();
|
||||||
|
|
||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'public' => $publicKey,
|
'public' => $publicKey,
|
||||||
|
@ -128,19 +128,8 @@ class KeyService
|
||||||
$this->permissionService->create($instance->id, $permission);
|
$this->permissionService->create($instance->id, $permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->database->commit();
|
$this->connection->commit();
|
||||||
|
|
||||||
return $secretKey;
|
return $secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the API key and associated permissions from the database.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return bool|null
|
|
||||||
*/
|
|
||||||
public function revoke($id)
|
|
||||||
{
|
|
||||||
return $this->repository->delete($id);
|
|
||||||
}
|
|
||||||
}
|
}
|
71
app/Services/Servers/ServerAccessHelperService.php
Normal file
71
app/Services/Servers/ServerAccessHelperService.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Services\Servers;
|
||||||
|
|
||||||
|
use Illuminate\Cache\Repository as CacheRepository;
|
||||||
|
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||||
|
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
|
||||||
|
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
|
||||||
|
class ServerAccessHelperService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
CacheRepository $cache,
|
||||||
|
ServerRepositoryInterface $repository,
|
||||||
|
SubuserRepositoryInterface $subuserRepository,
|
||||||
|
UserRepositoryInterface $userRepository
|
||||||
|
) {
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->repository = $repository;
|
||||||
|
$this->subuserRepository = $subuserRepository;
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle($uuid, $user)
|
||||||
|
{
|
||||||
|
if (! $user instanceof User) {
|
||||||
|
$user = $this->userRepository->find($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = $this->repository->getByUuid($uuid);
|
||||||
|
if (! $user->root_admin) {
|
||||||
|
if (! in_array($server->id, $this->repository->getUserAccessServers($user->id))) {
|
||||||
|
throw new \Exception('User does not have access.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($server->owner_id !== $user->id) {
|
||||||
|
$subuser = $this->subuserRepository->withColumns('daemonSecret')->findWhere([
|
||||||
|
['user_id', '=', $user->id],
|
||||||
|
['server_id', '=', $server->id],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server->daemonSecret = $subuser->daemonToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
}
|
||||||
|
}
|
84
app/Services/Users/ToggleTwoFactorService.php
Normal file
84
app/Services/Users/ToggleTwoFactorService.php
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Services\Users;
|
||||||
|
|
||||||
|
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||||
|
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||||
|
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
|
||||||
|
class ToggleTwoFactorService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||||
|
*/
|
||||||
|
protected $google2FA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ToggleTwoFactorService constructor.
|
||||||
|
*
|
||||||
|
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
|
||||||
|
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
Google2FA $google2FA,
|
||||||
|
UserRepositoryInterface $repository
|
||||||
|
) {
|
||||||
|
$this->google2FA = $google2FA;
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|\Pterodactyl\Models\User $user
|
||||||
|
* @param string $token
|
||||||
|
* @param null|bool $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)
|
||||||
|
{
|
||||||
|
if (! $user instanceof User) {
|
||||||
|
$user = $this->repository->find($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->google2FA->verifyKey($user->totp_secret, $token, 2)) {
|
||||||
|
throw new TwoFactorAuthenticationTokenInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->repository->withoutFresh()->update($user->id, [
|
||||||
|
'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
91
app/Services/Users/TwoFactorSetupService.php
Normal file
91
app/Services/Users/TwoFactorSetupService.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Pterodactyl\Services\Users;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||||
|
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||||
|
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
|
||||||
|
class TwoFactorSetupService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Config\Repository
|
||||||
|
*/
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||||
|
*/
|
||||||
|
protected $google2FA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TwoFactorSetupService constructor.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Contracts\Config\Repository $config
|
||||||
|
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
|
||||||
|
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
ConfigRepository $config,
|
||||||
|
Google2FA $google2FA,
|
||||||
|
UserRepositoryInterface $repository
|
||||||
|
) {
|
||||||
|
$this->config = $config;
|
||||||
|
$this->google2FA = $google2FA;
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a 2FA token and store it in the database.
|
||||||
|
*
|
||||||
|
* @param int|\Pterodactyl\Models\User $user
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
|
*/
|
||||||
|
public function handle($user)
|
||||||
|
{
|
||||||
|
if (! $user instanceof User) {
|
||||||
|
$user = $this->repository->find($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$secret = $this->google2FA->generateSecretKey();
|
||||||
|
$image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret);
|
||||||
|
|
||||||
|
$this->repository->withoutFresh()->update($user->id, ['totp_secret' => $secret]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'qrImage' => $image,
|
||||||
|
'secret' => $secret,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,6 +61,7 @@ class UserUpdateService
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
|
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||||
*/
|
*/
|
||||||
public function handle($id, array $data)
|
public function handle($id, array $data)
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,6 +33,7 @@ return [
|
||||||
'header_sub' => 'Manage your API access keys.',
|
'header_sub' => 'Manage your API access keys.',
|
||||||
'list' => 'API Keys',
|
'list' => 'API Keys',
|
||||||
'create_new' => 'Create New API key',
|
'create_new' => 'Create New API key',
|
||||||
|
'keypair_created' => 'An API Key-Pair has been generated. Your API secret token is <code>:token</code>. Please take note of this key as it will not be displayed again.',
|
||||||
],
|
],
|
||||||
'new' => [
|
'new' => [
|
||||||
'header' => 'New API Key',
|
'header' => 'New API Key',
|
||||||
|
@ -207,6 +208,8 @@ return [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'account' => [
|
'account' => [
|
||||||
|
'details_updated' => 'Your account details have been successfully updated.',
|
||||||
|
'invalid_password' => 'The password provided for your account was not valid.',
|
||||||
'header' => 'Your Account',
|
'header' => 'Your Account',
|
||||||
'header_sub' => 'Manage your account details.',
|
'header_sub' => 'Manage your account details.',
|
||||||
'update_pass' => 'Update Password',
|
'update_pass' => 'Update Password',
|
||||||
|
@ -219,10 +222,9 @@ return [
|
||||||
'last_name' => 'Last Name',
|
'last_name' => 'Last Name',
|
||||||
'update_identitity' => 'Update Identity',
|
'update_identitity' => 'Update Identity',
|
||||||
'username_help' => 'Your username must be unique to your account, and may only contain the following characters: :requirements.',
|
'username_help' => 'Your username must be unique to your account, and may only contain the following characters: :requirements.',
|
||||||
'invalid_pass' => 'The password provided was not valid for this account.',
|
|
||||||
'exception' => 'An error occurred while attempting to update your account.',
|
|
||||||
],
|
],
|
||||||
'security' => [
|
'security' => [
|
||||||
|
'session_mgmt_disabled' => 'Your host has not enabled the ability to manage account sessions via this interface.',
|
||||||
'header' => 'Account Security',
|
'header' => 'Account Security',
|
||||||
'header_sub' => 'Control active sessions and 2-Factor Authentication.',
|
'header_sub' => 'Control active sessions and 2-Factor Authentication.',
|
||||||
'sessions' => 'Active Sessions',
|
'sessions' => 'Active Sessions',
|
||||||
|
@ -234,5 +236,6 @@ return [
|
||||||
'enable_2fa' => 'Enable 2-Factor Authentication',
|
'enable_2fa' => 'Enable 2-Factor Authentication',
|
||||||
'2fa_qr' => 'Confgure 2FA on Your Device',
|
'2fa_qr' => 'Confgure 2FA on Your Device',
|
||||||
'2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.',
|
'2fa_checkpoint_help' => 'Use the 2FA application on your phone to take a picture of the QR code to the left, or manually enter the code under it. Once you have done so, generate a token and enter it below.',
|
||||||
|
'2fa_disable_error' => 'The 2FA token provided was not valid. Protection has not been disabled for this account.',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -39,30 +39,36 @@
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">@lang('base.security.sessions')</h3>
|
<h3 class="box-title">@lang('base.security.sessions')</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body table-responsive no-padding">
|
@if(!is_null($sessions))
|
||||||
<table class="table table-hover">
|
<div class="box-body table-responsive no-padding">
|
||||||
<tbody>
|
<table class="table table-hover">
|
||||||
<tr>
|
<tbody>
|
||||||
<th>@lang('strings.id')</th>
|
|
||||||
<th>@lang('strings.ip')</th>
|
|
||||||
<th>@lang('strings.last_activity')</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
@foreach($sessions as $session)
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>{{ substr($session->id, 0, 6) }}</code></td>
|
<th>@lang('strings.id')</th>
|
||||||
<td>{{ $session->ip_address }}</td>
|
<th>@lang('strings.ip')</th>
|
||||||
<td>{{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }}</td>
|
<th>@lang('strings.last_activity')</th>
|
||||||
<td>
|
<th></th>
|
||||||
<a href="{{ route('account.security.revoke', $session->id) }}">
|
|
||||||
<button class="btn btn-xs btn-danger"><i class="fa fa-trash-o"></i> @lang('strings.revoke')</button>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@foreach($sessions as $session)
|
||||||
</tbody>
|
<tr>
|
||||||
</table>
|
<td><code>{{ substr($session->id, 0, 6) }}</code></td>
|
||||||
</div>
|
<td>{{ $session->ip_address }}</td>
|
||||||
|
<td>{{ Carbon::createFromTimestamp($session->last_activity)->diffForHumans() }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('account.security.revoke', $session->id) }}">
|
||||||
|
<button class="btn btn-xs btn-danger"><i class="fa fa-trash-o"></i> @lang('strings.revoke')</button>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="box-body">
|
||||||
|
<p class="text-muted">@lang('base.security.session_mgmt_disabled')</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
|
@ -24,10 +24,6 @@
|
||||||
Route::get('/', 'IndexController@getIndex')->name('index');
|
Route::get('/', 'IndexController@getIndex')->name('index');
|
||||||
Route::get('/status/{server}', 'IndexController@status')->name('index.status');
|
Route::get('/status/{server}', 'IndexController@status')->name('index.status');
|
||||||
|
|
||||||
Route::get('/index', function () {
|
|
||||||
redirect()->route('index');
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Account Controller Routes
|
| Account Controller Routes
|
||||||
|
|
|
@ -83,4 +83,13 @@ trait ControllerAssertionsTrait
|
||||||
{
|
{
|
||||||
PHPUnit_Framework_Assert::assertEquals($value, array_get($view->getData(), $attribute));
|
PHPUnit_Framework_Assert::assertEquals($value, array_get($view->getData(), $attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $route
|
||||||
|
* @param \Illuminate\Http\RedirectResponse $response
|
||||||
|
*/
|
||||||
|
public function assertRouteRedirectEquals($route, $response)
|
||||||
|
{
|
||||||
|
PHPUnit_Framework_Assert::assertEquals(route($route), $response->getTargetUrl());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
132
tests/Unit/Http/Controllers/Base/AccountControllerTest.php
Normal file
132
tests/Unit/Http/Controllers/Base/AccountControllerTest.php
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Unit\Http\Controllers\Base;
|
||||||
|
|
||||||
|
use Mockery as m;
|
||||||
|
use Prologue\Alerts\AlertsMessageBag;
|
||||||
|
use Pterodactyl\Http\Controllers\Base\AccountController;
|
||||||
|
use Pterodactyl\Http\Requests\Base\AccountDataFormRequest;
|
||||||
|
use Pterodactyl\Services\Users\UserUpdateService;
|
||||||
|
use Tests\Assertions\ControllerAssertionsTrait;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AccountControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use ControllerAssertionsTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Prologue\Alerts\AlertsMessageBag
|
||||||
|
*/
|
||||||
|
protected $alert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Http\Controllers\Base\AccountController
|
||||||
|
*/
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Http\Requests\Base\AccountDataFormRequest
|
||||||
|
*/
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Users\UserUpdateService
|
||||||
|
*/
|
||||||
|
protected $updateService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup tests.
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->alert = m::mock(AlertsMessageBag::class);
|
||||||
|
$this->request = m::mock(AccountDataFormRequest::class);
|
||||||
|
$this->updateService = m::mock(UserUpdateService::class);
|
||||||
|
|
||||||
|
$this->controller = new AccountController($this->alert, $this->updateService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the index controller.
|
||||||
|
*/
|
||||||
|
public function testIndexController()
|
||||||
|
{
|
||||||
|
$response = $this->controller->index();
|
||||||
|
|
||||||
|
$this->assertViewNameEquals('base.account', $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test controller when password is being updated.
|
||||||
|
*/
|
||||||
|
public function testUpdateControllerForPassword()
|
||||||
|
{
|
||||||
|
$this->request->shouldReceive('input')->with('do_action')->andReturn('password');
|
||||||
|
$this->request->shouldReceive('input')->with('new_password')->once()->andReturn('test-password');
|
||||||
|
|
||||||
|
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn((object) ['id' => 1]);
|
||||||
|
$this->updateService->shouldReceive('handle')->with(1, ['password' => 'test-password'])->once()->andReturnNull();
|
||||||
|
$this->alert->shouldReceive('success->flash')->once()->andReturnNull();
|
||||||
|
|
||||||
|
$response = $this->controller->update($this->request);
|
||||||
|
$this->assertRouteRedirectEquals('account', $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test controller when email is being updated.
|
||||||
|
*/
|
||||||
|
public function testUpdateControllerForEmail()
|
||||||
|
{
|
||||||
|
$this->request->shouldReceive('input')->with('do_action')->andReturn('email');
|
||||||
|
$this->request->shouldReceive('input')->with('new_email')->once()->andReturn('test@example.com');
|
||||||
|
|
||||||
|
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn((object) ['id' => 1]);
|
||||||
|
$this->updateService->shouldReceive('handle')->with(1, ['email' => 'test@example.com'])->once()->andReturnNull();
|
||||||
|
$this->alert->shouldReceive('success->flash')->once()->andReturnNull();
|
||||||
|
|
||||||
|
$response = $this->controller->update($this->request);
|
||||||
|
$this->assertRouteRedirectEquals('account', $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test controller when identity is being updated.
|
||||||
|
*/
|
||||||
|
public function testUpdateControllerForIdentity()
|
||||||
|
{
|
||||||
|
$this->request->shouldReceive('input')->with('do_action')->andReturn('identity');
|
||||||
|
$this->request->shouldReceive('only')->with(['name_first', 'name_last', 'username'])->once()->andReturn([
|
||||||
|
'test_data' => 'value',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn((object) ['id' => 1]);
|
||||||
|
$this->updateService->shouldReceive('handle')->with(1, ['test_data' => 'value'])->once()->andReturnNull();
|
||||||
|
$this->alert->shouldReceive('success->flash')->once()->andReturnNull();
|
||||||
|
|
||||||
|
$response = $this->controller->update($this->request);
|
||||||
|
$this->assertRouteRedirectEquals('account', $response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,20 +27,20 @@ namespace Tests\Unit\Services\Api;
|
||||||
use Mockery as m;
|
use Mockery as m;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
use phpmock\phpunit\PHPMock;
|
use phpmock\phpunit\PHPMock;
|
||||||
use Pterodactyl\Services\Api\KeyService;
|
use Pterodactyl\Services\Api\KeyCreationService;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Illuminate\Contracts\Encryption\Encrypter;
|
use Illuminate\Contracts\Encryption\Encrypter;
|
||||||
use Pterodactyl\Services\Api\PermissionService;
|
use Pterodactyl\Services\Api\PermissionService;
|
||||||
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
|
||||||
|
|
||||||
class KeyServiceTest extends TestCase
|
class KeyCreationServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
use PHPMock;
|
use PHPMock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Illuminate\Database\ConnectionInterface
|
* @var \Illuminate\Database\ConnectionInterface
|
||||||
*/
|
*/
|
||||||
protected $database;
|
protected $connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||||
|
@ -58,7 +58,7 @@ class KeyServiceTest extends TestCase
|
||||||
protected $repository;
|
protected $repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Pterodactyl\Services\Api\KeyService
|
* @var \Pterodactyl\Services\Api\KeyCreationService
|
||||||
*/
|
*/
|
||||||
protected $service;
|
protected $service;
|
||||||
|
|
||||||
|
@ -66,14 +66,14 @@ class KeyServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->database = m::mock(ConnectionInterface::class);
|
$this->connection = m::mock(ConnectionInterface::class);
|
||||||
$this->encrypter = m::mock(Encrypter::class);
|
$this->encrypter = m::mock(Encrypter::class);
|
||||||
$this->permissions = m::mock(PermissionService::class);
|
$this->permissions = m::mock(PermissionService::class);
|
||||||
$this->repository = m::mock(ApiKeyRepositoryInterface::class);
|
$this->repository = m::mock(ApiKeyRepositoryInterface::class);
|
||||||
|
|
||||||
$this->service = new KeyService(
|
$this->service = new KeyCreationService(
|
||||||
$this->repository,
|
$this->repository,
|
||||||
$this->database,
|
$this->connection,
|
||||||
$this->encrypter,
|
$this->encrypter,
|
||||||
$this->permissions
|
$this->permissions
|
||||||
);
|
);
|
||||||
|
@ -82,21 +82,17 @@ class KeyServiceTest extends TestCase
|
||||||
/**
|
/**
|
||||||
* Test that the service is able to create a keypair and assign the correct permissions.
|
* Test that the service is able to create a keypair and assign the correct permissions.
|
||||||
*/
|
*/
|
||||||
public function test_create_function()
|
public function testKeyIsCreated()
|
||||||
{
|
{
|
||||||
$this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'random_bytes')
|
$this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'bin2hex')
|
||||||
->expects($this->exactly(2))
|
->expects($this->exactly(2))->willReturn('bin2hex');
|
||||||
->willReturnCallback(function ($bytes) {
|
|
||||||
return hex2bin(str_pad('', $bytes * 2, '0'));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
|
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
|
||||||
$this->encrypter->shouldReceive('encrypt')->with(str_pad('', 64, '0'))
|
$this->encrypter->shouldReceive('encrypt')->with('bin2hex')->once()->andReturn('encrypted-secret');
|
||||||
->once()->andReturn('encrypted-secret');
|
|
||||||
|
|
||||||
$this->repository->shouldReceive('create')->with([
|
$this->repository->shouldReceive('create')->with([
|
||||||
'test-data' => 'test',
|
'test-data' => 'test',
|
||||||
'public' => str_pad('', 16, '0'),
|
'public' => 'bin2hex',
|
||||||
'secret' => 'encrypted-secret',
|
'secret' => 'encrypted-secret',
|
||||||
], true, true)->once()->andReturn((object) ['id' => 1]);
|
], true, true)->once()->andReturn((object) ['id' => 1]);
|
||||||
|
|
||||||
|
@ -108,25 +104,15 @@ class KeyServiceTest extends TestCase
|
||||||
$this->permissions->shouldReceive('create')->with(1, 'user.server-list')->once()->andReturnNull();
|
$this->permissions->shouldReceive('create')->with(1, 'user.server-list')->once()->andReturnNull();
|
||||||
$this->permissions->shouldReceive('create')->with(1, 'server-create')->once()->andReturnNull();
|
$this->permissions->shouldReceive('create')->with(1, 'server-create')->once()->andReturnNull();
|
||||||
|
|
||||||
$this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
|
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
|
||||||
|
|
||||||
$response = $this->service->create(
|
$response = $this->service->handle(
|
||||||
['test-data' => 'test'],
|
['test-data' => 'test'],
|
||||||
['invalid-node', 'server-list'],
|
['invalid-node', 'server-list'],
|
||||||
['invalid-node', 'server-create']
|
['invalid-node', 'server-create']
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertNotEmpty($response);
|
$this->assertNotEmpty($response);
|
||||||
$this->assertEquals(str_pad('', 64, '0'), $response);
|
$this->assertEquals('bin2hex', $response);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that an API key can be revoked.
|
|
||||||
*/
|
|
||||||
public function test_revoke_function()
|
|
||||||
{
|
|
||||||
$this->repository->shouldReceive('delete')->with(1)->once()->andReturn(true);
|
|
||||||
|
|
||||||
$this->assertTrue($this->service->revoke(1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
132
tests/Unit/Services/Users/ToggleTwoFactorServiceTest.php
Normal file
132
tests/Unit/Services/Users/ToggleTwoFactorServiceTest.php
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Unit\Services\Users;
|
||||||
|
|
||||||
|
use Mockery as m;
|
||||||
|
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||||
|
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Pterodactyl\Services\Users\ToggleTwoFactorService;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ToggleTwoFactorServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||||
|
*/
|
||||||
|
protected $google2FA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
|
||||||
|
*/
|
||||||
|
protected $service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup tests.
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->google2FA = m::mock(Google2FA::class);
|
||||||
|
$this->repository = m::mock(UserRepositoryInterface::class);
|
||||||
|
|
||||||
|
$this->service = new ToggleTwoFactorService($this->google2FA, $this->repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that 2FA can be enabled for a user.
|
||||||
|
*/
|
||||||
|
public function testTwoFactorIsEnabledForUser()
|
||||||
|
{
|
||||||
|
$model = factory(User::class)->make(['totp_secret' => '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->assertTrue($this->service->handle($model, 'test-token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that 2FA can be disabled for a user.
|
||||||
|
*/
|
||||||
|
public function testTwoFactorIsDisabled()
|
||||||
|
{
|
||||||
|
$model = factory(User::class)->make(['totp_secret' => '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->assertTrue($this->service->handle($model, 'test-token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that 2FA will remain disabled for a user.
|
||||||
|
*/
|
||||||
|
public function testTwoFactorRemainsDisabledForUser()
|
||||||
|
{
|
||||||
|
$model = factory(User::class)->make(['totp_secret' => '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->assertTrue($this->service->handle($model, 'test-token', false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that an exception is thrown if the token provided is invalid.
|
||||||
|
*
|
||||||
|
* @expectedException \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
|
||||||
|
*/
|
||||||
|
public function testExceptionIsThrownIfTokenIsInvalid()
|
||||||
|
{
|
||||||
|
$model = factory(User::class)->make();
|
||||||
|
$this->google2FA->shouldReceive('verifyKey')->once()->andReturn(false);
|
||||||
|
|
||||||
|
$this->service->handle($model, 'test-token');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that an integer can be passed in place of a user model.
|
||||||
|
*/
|
||||||
|
public function testIntegerCanBePassedInPlaceOfUserModel()
|
||||||
|
{
|
||||||
|
$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'));
|
||||||
|
}
|
||||||
|
}
|
108
tests/Unit/Services/Users/TwoFactorSetupServiceTest.php
Normal file
108
tests/Unit/Services/Users/TwoFactorSetupServiceTest.php
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pterodactyl - Panel
|
||||||
|
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Unit\Services\Users;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Config\Repository;
|
||||||
|
use Mockery as m;
|
||||||
|
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||||
|
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||||
|
use Pterodactyl\Models\User;
|
||||||
|
use Pterodactyl\Services\Users\TwoFactorSetupService;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class TwoFactorSetupServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Illuminate\Contracts\Config\Repository
|
||||||
|
*/
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||||
|
*/
|
||||||
|
protected $google2FA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
|
||||||
|
*/
|
||||||
|
protected $service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup tests.
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->config = m::mock(Repository::class);
|
||||||
|
$this->google2FA = m::mock(Google2FA::class);
|
||||||
|
$this->repository = m::mock(UserRepositoryInterface::class);
|
||||||
|
|
||||||
|
$this->service = new TwoFactorSetupService($this->config, $this->google2FA, $this->repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the correct data is returned.
|
||||||
|
*/
|
||||||
|
public function testSecretAndImageAreReturned()
|
||||||
|
{
|
||||||
|
$model = factory(User::class)->make();
|
||||||
|
|
||||||
|
$this->google2FA->shouldReceive('generateSecretKey')->withNoArgs()->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();
|
||||||
|
|
||||||
|
$response = $this->service->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']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that an integer can be passed in place of the user model.
|
||||||
|
*/
|
||||||
|
public function testIntegerCanBePassedInPlaceOfUserModel()
|
||||||
|
{
|
||||||
|
$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)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue