Implement changes to administrative user revocation, closes #733
This commit is contained in:
parent
20beb2f280
commit
975597b4d0
19 changed files with 458 additions and 125 deletions
|
@ -78,8 +78,10 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface
|
|||
/**
|
||||
* Revoke an access key on the daemon before the time is expired.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|array $key
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \GuzzleHttp\Exception\RequestException
|
||||
*/
|
||||
public function revokeAccessKey($key);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
|
||||
namespace Pterodactyl\Contracts\Repository;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\DaemonKey;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface DaemonKeyRepositoryInterface extends RepositoryInterface
|
||||
{
|
||||
|
@ -59,4 +61,22 @@ interface DaemonKeyRepositoryInterface extends RepositoryInterface
|
|||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function getKeyWithServer($key);
|
||||
|
||||
/**
|
||||
* Get all of the keys for a specific user including the information needed
|
||||
* from their server relation for revocation on the daemon.
|
||||
*
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getKeysForRevocation(User $user): Collection;
|
||||
|
||||
/**
|
||||
* Delete an array of daemon keys from the database. Used primarily in
|
||||
* conjunction with getKeysForRevocation.
|
||||
*
|
||||
* @param array $ids
|
||||
* @return bool|int
|
||||
*/
|
||||
public function deleteKeys(array $ids);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Exceptions\Http\Connection;
|
||||
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Admin;
|
||||
|
||||
|
@ -160,10 +153,30 @@ class UserController extends Controller
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function update(UserFormRequest $request, User $user)
|
||||
{
|
||||
$this->updateService->handle($user->id, $request->normalize());
|
||||
$this->updateService->setUserLevel(User::USER_LEVEL_ADMIN);
|
||||
$data = $this->updateService->handle($user, $request->normalize());
|
||||
|
||||
if (! empty($data->get('exceptions'))) {
|
||||
foreach ($data->get('exceptions') as $node => $exception) {
|
||||
/** @var \GuzzleHttp\Exception\RequestException $exception */
|
||||
/** @var \GuzzleHttp\Psr7\Response|null $response */
|
||||
$response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null;
|
||||
$message = trans('admin/server.exceptions.daemon_exception', [
|
||||
'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
|
||||
]);
|
||||
|
||||
$this->alert->danger(trans('exceptions.users.node_revocation_failed', [
|
||||
'node' => $node,
|
||||
'error' => $message,
|
||||
'link' => route('admin.nodes.view', $node),
|
||||
]))->flash();
|
||||
}
|
||||
}
|
||||
|
||||
$this->alert->success($this->translator->trans('admin/user.notices.account_updated'))->flash();
|
||||
|
||||
return redirect()->route('admin.users.view', $user->id);
|
||||
|
|
|
@ -1,30 +1,8 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>
|
||||
* Some Modifications (c) 2015 Dylan Seidt <dylan.seidt@gmail.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\Controllers\Base;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Users\UserUpdateService;
|
||||
|
@ -48,10 +26,8 @@ class AccountController extends Controller
|
|||
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
||||
* @param \Pterodactyl\Services\Users\UserUpdateService $updateService
|
||||
*/
|
||||
public function __construct(
|
||||
AlertsMessageBag $alert,
|
||||
UserUpdateService $updateService
|
||||
) {
|
||||
public function __construct(AlertsMessageBag $alert, UserUpdateService $updateService)
|
||||
{
|
||||
$this->alert = $alert;
|
||||
$this->updateService = $updateService;
|
||||
}
|
||||
|
@ -74,6 +50,7 @@ class AccountController extends Controller
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function update(AccountDataFormRequest $request)
|
||||
{
|
||||
|
@ -86,7 +63,8 @@ class AccountController extends Controller
|
|||
$data = $request->only(['name_first', 'name_last', 'username']);
|
||||
}
|
||||
|
||||
$this->updateService->handle($request->user()->id, $data);
|
||||
$this->updateService->setUserLevel(User::USER_LEVEL_USER);
|
||||
$this->updateService->handle($request->user(), $data);
|
||||
$this->alert->success(trans('base.account.details_updated'))->flash();
|
||||
|
||||
return redirect()->route('account');
|
||||
|
|
|
@ -21,6 +21,8 @@ class AdminAuthenticate
|
|||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
|
|
|
@ -46,6 +46,8 @@ class DaemonAuthenticate
|
|||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
|
|
|
@ -47,9 +47,8 @@ class AuthenticateAsSubuser
|
|||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Illuminate\Auth\AuthenticationException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
|
|
|
@ -19,7 +19,11 @@ class UserFormRequest extends AdminFormRequest
|
|||
public function rules()
|
||||
{
|
||||
if ($this->method() === 'PATCH') {
|
||||
return User::getUpdateRulesForId($this->route()->parameter('user')->id);
|
||||
$rules = User::getUpdateRulesForId($this->route()->parameter('user')->id);
|
||||
|
||||
return array_merge($rules, [
|
||||
'ignore_connection_error' => 'sometimes|nullable|boolean',
|
||||
]);
|
||||
}
|
||||
|
||||
return User::getCreateRules();
|
||||
|
@ -30,7 +34,7 @@ class UserFormRequest extends AdminFormRequest
|
|||
if ($this->method === 'PATCH') {
|
||||
return array_merge(
|
||||
$this->intersect('password'),
|
||||
$this->only(['email', 'username', 'name_first', 'name_last', 'root_admin'])
|
||||
$this->only(['email', 'username', 'name_first', 'name_last', 'root_admin', 'ignore_connection_error'])
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,13 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa
|
|||
*/
|
||||
public function revokeAccessKey($key)
|
||||
{
|
||||
Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string, received %s.');
|
||||
if (is_array($key)) {
|
||||
return $this->getHttpClient()->request('POST', 'keys', [
|
||||
'json' => $key,
|
||||
]);
|
||||
}
|
||||
|
||||
Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string or array, received %s.');
|
||||
|
||||
return $this->getHttpClient()->request('DELETE', 'keys/' . $key);
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@
|
|||
|
||||
namespace Pterodactyl\Repositories\Eloquent;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\DaemonKey;
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
|
||||
|
||||
|
@ -83,4 +85,28 @@ class DaemonKeyRepository extends EloquentRepository implements DaemonKeyReposit
|
|||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the keys for a specific user including the information needed
|
||||
* from their server relation for revocation on the daemon.
|
||||
*
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getKeysForRevocation(User $user): Collection
|
||||
{
|
||||
return $this->getBuilder()->with('server:id,uuid,node_id')->where('user_id', $user->id)->get($this->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an array of daemon keys from the database. Used primarily in
|
||||
* conjunction with getKeysForRevocation.
|
||||
*
|
||||
* @param array $ids
|
||||
* @return bool|int
|
||||
*/
|
||||
public function deleteKeys(array $ids)
|
||||
{
|
||||
return $this->getBuilder()->whereIn('id', $ids)->delete();
|
||||
}
|
||||
}
|
||||
|
|
91
app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php
Normal file
91
app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\DaemonKeys;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepository;
|
||||
|
||||
class RevokeMultipleDaemonKeysService
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $exceptions = [];
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
|
||||
*/
|
||||
private $daemonRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* RevokeMultipleDaemonKeysService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository
|
||||
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
|
||||
*/
|
||||
public function __construct(
|
||||
DaemonKeyRepositoryInterface $repository,
|
||||
DaemonServerRepository $daemonRepository
|
||||
) {
|
||||
$this->daemonRepository = $daemonRepository;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab all of the keys that exist for a single user and delete them from all
|
||||
* daemon's that they are assigned to. If connection fails, this function will
|
||||
* return an error.
|
||||
*
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @param bool $ignoreConnectionErrors
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function handle(User $user, bool $ignoreConnectionErrors = false)
|
||||
{
|
||||
$keys = $this->repository->getKeysForRevocation($user);
|
||||
|
||||
$keys->groupBy('server.node_id')->each(function ($group, $node) use ($ignoreConnectionErrors) {
|
||||
try {
|
||||
$this->daemonRepository->setNode($node)->revokeAccessKey(collect($group)->pluck('secret')->toArray());
|
||||
} catch (RequestException $exception) {
|
||||
if (! $ignoreConnectionErrors) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
|
||||
$this->setConnectionException($node, $exception);
|
||||
}
|
||||
|
||||
$this->repository->deleteKeys(collect($group)->pluck('id')->toArray());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of exceptions that were returned by the handle function.
|
||||
*
|
||||
* @return RequestException[]
|
||||
*/
|
||||
public function getExceptions()
|
||||
{
|
||||
return $this->exceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an exception for a node to the array.
|
||||
*
|
||||
* @param int $node
|
||||
* @param \GuzzleHttp\Exception\RequestException $exception
|
||||
*/
|
||||
protected function setConnectionException(int $node, RequestException $exception)
|
||||
{
|
||||
$this->exceptions[$node] = $exception;
|
||||
}
|
||||
}
|
|
@ -1,59 +1,79 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Services\Users;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Pterodactyl\Traits\Services\HasUserLevels;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService;
|
||||
|
||||
class UserUpdateService
|
||||
{
|
||||
use HasUserLevels;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Hashing\Hasher
|
||||
*/
|
||||
protected $hasher;
|
||||
private $hasher;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||
*/
|
||||
protected $repository;
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService
|
||||
*/
|
||||
private $revocationService;
|
||||
|
||||
/**
|
||||
* UpdateService constructor.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
|
||||
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
|
||||
* @param \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService $revocationService
|
||||
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(
|
||||
Hasher $hasher,
|
||||
RevokeMultipleDaemonKeysService $revocationService,
|
||||
UserRepositoryInterface $repository
|
||||
) {
|
||||
$this->hasher = $hasher;
|
||||
$this->repository = $repository;
|
||||
$this->revocationService = $revocationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user model instance.
|
||||
* Update the user model instance. If the user has been removed as an administrator
|
||||
* revoke all of the authentication tokens that have beenn assigned to their account.
|
||||
*
|
||||
* @param int $id
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @param array $data
|
||||
* @return \Illuminate\Support\Collection
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function handle($id, array $data)
|
||||
public function handle(User $user, array $data): Collection
|
||||
{
|
||||
if (isset($data['password'])) {
|
||||
if (array_has($data, 'password')) {
|
||||
$data['password'] = $this->hasher->make($data['password']);
|
||||
}
|
||||
|
||||
return $this->repository->update($id, $data);
|
||||
if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) {
|
||||
if (array_get($data, 'root_admin', 0) == 0 && $user->root_admin) {
|
||||
$this->revocationService->handle($user, array_get($data, 'ignore_connection_error', false));
|
||||
}
|
||||
} else {
|
||||
unset($data['root_admin']);
|
||||
}
|
||||
|
||||
return collect([
|
||||
'model' => $this->repository->update($user->id, $data),
|
||||
'exceptions' => $this->revocationService->getExceptions(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue