From a6f46d36ba2617a06b16ee9db57441cf7dc96117 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 27 Mar 2020 14:23:13 -0700 Subject: [PATCH] Implement basic code for creating/updating a subuser --- .../Api/Client/Servers/SubuserController.php | 89 ++++++++++++++- .../Subusers/AbstractSubuserRequest.php | 63 +++++++++++ .../Servers/Subusers/DeleteSubuserRequest.php | 16 +++ .../Servers/Subusers/StoreSubuserRequest.php | 29 +++++ .../Servers/Subusers/UpdateSubuserRequest.php | 27 +++++ app/Models/Validable.php | 2 +- .../Eloquent/SubuserRepository.php | 27 +++-- .../Subusers/PermissionCreationService.php | 63 ----------- .../Subusers/SubuserCreationService.php | 105 +++++++---------- .../Subusers/SubuserDeletionService.php | 35 ------ .../Subusers/SubuserUpdateService.php | 107 ------------------ .../Api/Client/SubuserTransformer.php | 26 +---- .../api/server/users/createOrUpdateSubuser.ts | 18 +++ .../api/server/users/getServerSubusers.ts | 6 +- .../server/users/EditSubuserModal.tsx | 50 ++++++-- resources/scripts/state/server/subusers.ts | 2 +- routes/api-client.php | 4 + 17 files changed, 347 insertions(+), 322 deletions(-) create mode 100644 app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php create mode 100644 app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php create mode 100644 app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php create mode 100644 app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php delete mode 100644 app/Services/Subusers/PermissionCreationService.php delete mode 100644 app/Services/Subusers/SubuserDeletionService.php delete mode 100644 app/Services/Subusers/SubuserUpdateService.php create mode 100644 resources/scripts/api/server/users/createOrUpdateSubuser.ts diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 3b3746b04..ed4929c07 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -2,11 +2,17 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; +use Illuminate\Http\Request; use Pterodactyl\Models\Server; +use Illuminate\Http\JsonResponse; use Pterodactyl\Repositories\Eloquent\SubuserRepository; +use Pterodactyl\Services\Subusers\SubuserCreationService; use Pterodactyl\Transformers\Api\Client\SubuserTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest; class SubuserController extends ClientApiController { @@ -15,16 +21,25 @@ class SubuserController extends ClientApiController */ private $repository; + /** + * @var \Pterodactyl\Services\Subusers\SubuserCreationService + */ + private $creationService; + /** * SubuserController constructor. * * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository + * @param \Pterodactyl\Services\Subusers\SubuserCreationService $creationService */ - public function __construct(SubuserRepository $repository) - { + public function __construct( + SubuserRepository $repository, + SubuserCreationService $creationService + ) { parent::__construct(); $this->repository = $repository; + $this->creationService = $creationService; } /** @@ -36,10 +51,76 @@ class SubuserController extends ClientApiController */ public function index(GetSubuserRequest $request, Server $server) { - $server->subusers->load('user'); - return $this->fractal->collection($server->subusers) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); } + + /** + * Create a new subuser for the given server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest $request + * @param \Pterodactyl\Models\Server $server + * @return array + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException + * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws \Throwable + */ + public function store(StoreSubuserRequest $request, Server $server) + { + $response = $this->creationService->handle( + $server, $request->input('email'), $this->getDefaultPermissions($request) + ); + + return $this->fractal->item($response) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Update a given subuser in the system for the server. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request + * + * @return array + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateSubuserRequest $request) + { + $subuser = $request->subuser(); + $this->repository->update($subuser->id, [ + 'permissions' => $this->getDefaultPermissions($request), + ]); + + return $this->fractal->item($subuser->refresh()) + ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->toArray(); + } + + /** + * Removes a subusers from a server's assignment. + * + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function delete(DeleteSubuserRequest $request) + { + $this->repository->delete($request->subuser()->id); + + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); + } + + /** + * Returns the default permissions for all subusers to ensure none are ever removed wrongly. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function getDefaultPermissions(Request $request): array + { + return array_merge($request->input('permissions') ?? [], ['websocket.*']); + } } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php new file mode 100644 index 000000000..09d14545c --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php @@ -0,0 +1,63 @@ +subuser()->user_id === $this->user()->id) { + return false; + } + + return true; + } + + /** + * Return the subuser model for the given request which can then be validated. If + * required request parameters are missing a 404 error will be returned, otherwise + * a model exception will be returned if the model is not found. + * + * @return \Pterodactyl\Models\Subuser + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function subuser() + { + /** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */ + $repository = $this->container->make(SubuserRepository::class); + + $parameters = $this->route()->parameters(); + if ( + ! isset($parameters['server'], $parameters['server']) + || ! is_string($parameters['subuser']) + || ! $parameters['server'] instanceof Server + ) { + throw new NotFoundHttpException; + } + + return $this->model ?: $this->model = $repository->getUserForServer( + $this->route()->parameter('subuser'), $this->route()->parameter('server')->id + ); + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php new file mode 100644 index 000000000..e9d05e2c0 --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php @@ -0,0 +1,16 @@ + 'required|email', + 'permissions' => 'required|array', + 'permissions.*' => 'string', + ]; + } +} diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php new file mode 100644 index 000000000..41cdeb5ac --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php @@ -0,0 +1,27 @@ + 'required|array', + 'permissions.*' => 'string', + ]; + } +} diff --git a/app/Models/Validable.php b/app/Models/Validable.php index e7c5c5665..43545c3a8 100644 --- a/app/Models/Validable.php +++ b/app/Models/Validable.php @@ -140,7 +140,7 @@ abstract class Validable extends Model } return $this->getValidator()->setData( - $this->getAttributes() + $this->toArray() )->passes(); } } diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php index 4636f7b37..e00d825e7 100644 --- a/app/Repositories/Eloquent/SubuserRepository.php +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Subuser; -use Illuminate\Support\Collection; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; @@ -20,19 +19,27 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI } /** - * Returns the subusers for the given server instance with the associated user - * and permission relationships pre-loaded. + * Returns a subuser model for the given user and server combination. If no record + * exists an exception will be thrown. * * @param int $server - * @return \Illuminate\Support\Collection + * @param string $uuid + * @return \Pterodactyl\Models\Subuser + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public function getSubusersForServer(int $server): Collection + public function getUserForServer(int $server, string $uuid): Subuser { - return $this->getBuilder() - ->with('user', 'permissions') - ->where('server_id', $server) - ->get() - ->toBase(); + /** @var \Pterodactyl\Models\Subuser $model */ + $model = $this->getBuilder() + ->with('server', 'user') + ->select('subusers.*') + ->join('users', 'users.id', '=', 'subusers.user_id') + ->where('subusers.server_id', $server) + ->where('users.uuid', $uuid) + ->firstOrFail(); + + return $model; } /** diff --git a/app/Services/Subusers/PermissionCreationService.php b/app/Services/Subusers/PermissionCreationService.php deleted file mode 100644 index 328485ee1..000000000 --- a/app/Services/Subusers/PermissionCreationService.php +++ /dev/null @@ -1,63 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Webmozart\Assert\Assert; -use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; - -class PermissionCreationService -{ - /** - * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface - */ - protected $repository; - - /** - * PermissionCreationService constructor. - * - * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $repository - */ - public function __construct(PermissionRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Assign permissions to a given subuser. - * - * @param int $subuser - * @param array $permissions - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle($subuser, array $permissions) - { - Assert::integerish($subuser, 'First argument passed to handle must be an integer, received %s.'); - - $permissionMappings = Permission::getPermissions(true); - $insertPermissions = []; - - foreach ($permissions as $permission) { - if (array_key_exists($permission, $permissionMappings)) { - Assert::stringNotEmpty($permission, 'Permission argument provided must be a non-empty string, received %s.'); - - array_push($insertPermissions, [ - 'subuser_id' => $subuser, - 'permission' => $permission, - ]); - } - } - - if (! empty($insertPermissions)) { - $this->repository->withoutFreshModel()->insert($insertPermissions); - } - } -} diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 9c6b7c42e..ea8b5c2be 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -10,13 +10,12 @@ namespace Pterodactyl\Services\Subusers; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Users\UserCreationService; +use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException; use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; @@ -25,113 +24,87 @@ class SubuserCreationService /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService + * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository */ - protected $keyCreationService; - - /** - * @var \Pterodactyl\Services\Subusers\PermissionCreationService - */ - protected $permissionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - protected $subuserRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; + private $subuserRepository; /** * @var \Pterodactyl\Services\Users\UserCreationService */ - protected $userCreationService; + private $userCreationService; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $userRepository; + private $userRepository; /** * SubuserCreationService constructor. * * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository + * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $subuserRepository * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository */ public function __construct( ConnectionInterface $connection, - DaemonKeyCreationService $keyCreationService, - PermissionCreationService $permissionService, - ServerRepositoryInterface $serverRepository, - SubuserRepositoryInterface $subuserRepository, + SubuserRepository $subuserRepository, UserCreationService $userCreationService, UserRepositoryInterface $userRepository ) { $this->connection = $connection; - $this->keyCreationService = $keyCreationService; - $this->permissionService = $permissionService; - $this->serverRepository = $serverRepository; $this->subuserRepository = $subuserRepository; $this->userRepository = $userRepository; $this->userCreationService = $userCreationService; } /** - * @param int|\Pterodactyl\Models\Server $server + * Creates a new user on the system and assigns them access to the provided server. + * If the email address already belongs to a user on the system a new user will not + * be created. + * + * @param \Pterodactyl\Models\Server $server * @param string $email * @param array $permissions * @return \Pterodactyl\Models\Subuser * - * @throws \Exception * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException + * @throws \Throwable */ - public function handle($server, $email, array $permissions) + public function handle(Server $server, string $email, array $permissions): Subuser { - if (! $server instanceof Server) { - $server = $this->serverRepository->find($server); - } + return $this->connection->transaction(function () use ($server, $email, $permissions) { + try { + $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); - $this->connection->beginTransaction(); - try { - $user = $this->userRepository->findFirstWhere([['email', '=', $email]]); + if ($server->owner_id === $user->id) { + throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + } - if ($server->owner_id === $user->id) { - throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); + $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); + if ($subuserCount !== 0) { + throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); + } + } catch (RecordNotFoundException $exception) { + $user = $this->userCreationService->handle([ + 'email' => $email, + 'username' => preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')) . str_random(3), + 'name_first' => 'Server', + 'name_last' => 'Subuser', + 'root_admin' => false, + ]); } - $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); - if ($subuserCount !== 0) { - throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); - } - } catch (RecordNotFoundException $exception) { - $username = preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')); - $user = $this->userCreationService->handle([ - 'email' => $email, - 'username' => $username . str_random(3), - 'name_first' => 'Server', - 'name_last' => 'Subuser', - 'root_admin' => false, + return $this->subuserRepository->create([ + 'user_id' => $user->id, + 'server_id' => $server->id, + 'permissions' => $permissions, ]); - } - - $subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]); - $this->keyCreationService->handle($server->id, $user->id); - $this->permissionService->handle($subuser->id, $permissions); - $this->connection->commit(); - - return $subuser; + }); } } diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php deleted file mode 100644 index 6bc35ae3b..000000000 --- a/app/Services/Subusers/SubuserDeletionService.php +++ /dev/null @@ -1,35 +0,0 @@ -repository = $repository; - } - - /** - * Delete a subuser and their associated permissions from the Panel and Daemon. - * - * @param \Pterodactyl\Models\Subuser $subuser - */ - public function handle(Subuser $subuser) - { - $this->repository->delete($subuser->id); - } -} diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php deleted file mode 100644 index 64d7f0b38..000000000 --- a/app/Services/Subusers/SubuserUpdateService.php +++ /dev/null @@ -1,107 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Subusers; - -use Pterodactyl\Models\Subuser; -use GuzzleHttp\Exception\RequestException; -use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; -use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; - -class SubuserUpdateService -{ - /** - * @var \Illuminate\Database\ConnectionInterface - */ - private $connection; - - /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface - */ - private $daemonRepository; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - private $keyProviderService; - - /** - * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface - */ - private $permissionRepository; - - /** - * @var \Pterodactyl\Services\Subusers\PermissionCreationService - */ - private $permissionService; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - private $repository; - - /** - * SubuserUpdateService constructor. - * - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService - * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository - */ - public function __construct( - ConnectionInterface $connection, - DaemonKeyProviderService $keyProviderService, - DaemonServerRepositoryInterface $daemonRepository, - PermissionCreationService $permissionService, - PermissionRepositoryInterface $permissionRepository, - SubuserRepositoryInterface $repository - ) { - $this->connection = $connection; - $this->daemonRepository = $daemonRepository; - $this->keyProviderService = $keyProviderService; - $this->permissionRepository = $permissionRepository; - $this->permissionService = $permissionService; - $this->repository = $repository; - } - - /** - * Update permissions for a given subuser. - * - * @param \Pterodactyl\Models\Subuser $subuser - * @param array $permissions - * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Subuser $subuser, array $permissions) - { - $subuser = $this->repository->loadServerAndUserRelations($subuser); - - $this->connection->beginTransaction(); - $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); - $this->permissionService->handle($subuser->id, $permissions); - - try { - $token = $this->keyProviderService->handle($subuser->getRelation('server'), $subuser->getRelation('user'), false); - $this->daemonRepository->setServer($subuser->getRelation('server'))->revokeAccessKey($token); - } catch (RequestException $exception) { - $this->connection->rollBack(); - throw new DaemonConnectionException($exception); - } - - $this->connection->commit(); - } -} diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index e0b165553..d2e7ce0ff 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -2,16 +2,10 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\User; use Pterodactyl\Models\Subuser; class SubuserTransformer extends BaseClientTransformer { - /** - * @var array - */ - protected $defaultIncludes = ['user']; - /** * Return the resource name for the JSONAPI output. * @@ -27,23 +21,13 @@ class SubuserTransformer extends BaseClientTransformer * * @param \Pterodactyl\Models\Subuser $model * @return array|void + * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function transform(Subuser $model) { - return [ - 'permissions' => $model->permissions->pluck('permission'), - ]; - } - - /** - * Include the permissions associated with this subuser. - * - * @param \Pterodactyl\Models\Subuser $model - * @return \League\Fractal\Resource\Item - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function includeUser(Subuser $model) - { - return $this->item($model->user, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME); + return array_merge( + $this->makeTransformer(UserTransformer::class)->transform($model->user), + ['permissions' => $model->permissions] + ); } } diff --git a/resources/scripts/api/server/users/createOrUpdateSubuser.ts b/resources/scripts/api/server/users/createOrUpdateSubuser.ts new file mode 100644 index 000000000..fbcf78fe5 --- /dev/null +++ b/resources/scripts/api/server/users/createOrUpdateSubuser.ts @@ -0,0 +1,18 @@ +import http from '@/api/http'; +import { rawDataToServerSubuser } from '@/api/server/users/getServerSubusers'; +import { Subuser } from '@/state/server/subusers'; + +interface Params { + email: string; + permissions: string[]; +} + +export default (uuid: string, params: Params, subuser?: Subuser): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/users${subuser ? `/${subuser.uuid}` : ''}`, { + ...params, + }) + .then(data => resolve(rawDataToServerSubuser(data.data))) + .catch(reject); + }); +} diff --git a/resources/scripts/api/server/users/getServerSubusers.ts b/resources/scripts/api/server/users/getServerSubusers.ts index 0d290549d..177bde815 100644 --- a/resources/scripts/api/server/users/getServerSubusers.ts +++ b/resources/scripts/api/server/users/getServerSubusers.ts @@ -8,13 +8,13 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({ image: data.attributes.image, twoFactorEnabled: data.attributes['2fa_enabled'], createdAt: new Date(data.attributes.created_at), - permissions: data.attributes.relationships!.permissions.attributes.permissions, - can: permission => data.attributes.relationships!.permissions.attributes.permissions.indexOf(permission) >= 0, + permissions: data.attributes.permissions || [], + can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0, }); export default (uuid: string): Promise => { return new Promise((resolve, reject) => { - http.get(`/api/client/servers/${uuid}/users`, { params: { include: [ 'permissions' ] } }) + http.get(`/api/client/servers/${uuid}/users`) .then(({ data }) => resolve((data.data || []).map(rawDataToServerSubuser))) .catch(reject); }); diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index 65ff2aaa2..281e52a58 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -1,15 +1,19 @@ -import React from 'react'; +import React, { forwardRef, MutableRefObject, useRef } from 'react'; import { Subuser } from '@/state/server/subusers'; -import { Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { array, object, string } from 'yup'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import Field from '@/components/elements/Field'; -import { useStoreState } from 'easy-peasy'; +import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import Checkbox from '@/components/elements/Checkbox'; import styled from 'styled-components'; import classNames from 'classnames'; +import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser'; +import { ServerContext } from '@/state/server'; +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; type Props = { subuser?: Subuser; @@ -29,13 +33,14 @@ const PermissionLabel = styled.label` } `; -const EditSubuserModal = ({ subuser, ...props }: Props) => { +const EditSubuserModal = forwardRef(({ subuser, ...props }, ref) => { const { values, isSubmitting, setFieldValue } = useFormikContext(); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); return ( -

{subuser ? 'Edit subuser' : 'Create new subuser'}

+

{subuser ? 'Edit subuser' : 'Create new subuser'}

+
{
); -}; +}); -export default (props: Props) => { - const submit = (values: Values, helpers: FormikHelpers) => { +export default ({ subuser, ...props }: Props) => { + const ref = useRef(null); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); + + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('user:edit'); + createOrUpdateSubuser(uuid, values, subuser) + .then(subuser => { + appendSubuser(subuser); + props.onDismissed(); + }) + .catch(error => { + console.error(error); + setSubmitting(false); + addError({ key: 'user:edit', message: httpErrorToHuman(error) }); + + if (ref.current) { + ref.current.scrollIntoView(); + } + }); }; return ( - +
+ +
); }; diff --git a/resources/scripts/state/server/subusers.ts b/resources/scripts/state/server/subusers.ts index 5d15149b7..5cba6f3a5 100644 --- a/resources/scripts/state/server/subusers.ts +++ b/resources/scripts/state/server/subusers.ts @@ -39,7 +39,7 @@ const subusers: ServerSubuserStore = { }), appendSubuser: action((state, payload) => { - state.data = [ ...state.data, payload ]; + state.data = [ ...state.data.filter(user => user.uuid !== payload.uuid), payload ]; }), getSubusers: thunk(async (actions, payload) => { diff --git a/routes/api-client.php b/routes/api-client.php index 7aa8e6252..f623d5ec2 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -81,6 +81,10 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::group(['prefix' => '/users'], function () { Route::get('/', 'Servers\SubuserController@index'); + Route::post('/', 'Servers\SubuserController@store'); + Route::get('/{subuser}', 'Servers\SubuserController@view'); + Route::post('/{subuser}', 'Servers\SubuserController@update'); + Route::delete('/{subuser}', 'Servers\SubuserController@delete'); }); Route::group(['prefix' => '/settings'], function () {