Implement basic code for creating/updating a subuser

This commit is contained in:
Dane Everitt 2020-03-27 14:23:13 -07:00
parent 51c5cf4dbb
commit a6f46d36ba
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
17 changed files with 347 additions and 322 deletions

View file

@ -2,11 +2,17 @@
namespace Pterodactyl\Http\Controllers\Api\Client\Servers; namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Illuminate\Http\Request;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Repositories\Eloquent\SubuserRepository;
use Pterodactyl\Services\Subusers\SubuserCreationService;
use Pterodactyl\Transformers\Api\Client\SubuserTransformer; use Pterodactyl\Transformers\Api\Client\SubuserTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest; 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 class SubuserController extends ClientApiController
{ {
@ -15,16 +21,25 @@ class SubuserController extends ClientApiController
*/ */
private $repository; private $repository;
/**
* @var \Pterodactyl\Services\Subusers\SubuserCreationService
*/
private $creationService;
/** /**
* SubuserController constructor. * SubuserController constructor.
* *
* @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository * @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(); parent::__construct();
$this->repository = $repository; $this->repository = $repository;
$this->creationService = $creationService;
} }
/** /**
@ -36,10 +51,76 @@ class SubuserController extends ClientApiController
*/ */
public function index(GetSubuserRequest $request, Server $server) public function index(GetSubuserRequest $request, Server $server)
{ {
$server->subusers->load('user');
return $this->fractal->collection($server->subusers) return $this->fractal->collection($server->subusers)
->transformWith($this->getTransformer(SubuserTransformer::class)) ->transformWith($this->getTransformer(SubuserTransformer::class))
->toArray(); ->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.*']);
}
} }

View file

@ -0,0 +1,63 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
abstract class AbstractSubuserRequest extends ClientApiRequest
{
/**
* @var \Pterodactyl\Models\Subuser|null
*/
protected $model;
/**
* Authorize the request and ensure that a user is not trying to modify themselves.
*
* @return bool
*/
public function authorize(): bool
{
if (! parent::authorize()) {
return false;
}
if ($this->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
);
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
use Pterodactyl\Models\Permission;
class DeleteSubuserRequest extends AbstractSubuserRequest
{
/**
* @return string
*/
public function permission()
{
return Permission::ACTION_USER_DELETE;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
use Pterodactyl\Models\Permission;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class StoreSubuserRequest extends ClientApiRequest
{
/**
* @return string
*/
public function permission()
{
return Permission::ACTION_USER_CREATE;
}
/**
* @return array
*/
public function rules(): array
{
return [
'email' => 'required|email',
'permissions' => 'required|array',
'permissions.*' => 'string',
];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
use Pterodactyl\Models\Permission;
class UpdateSubuserRequest extends AbstractSubuserRequest
{
/**
* @return string
*/
public function permission()
{
return Permission::ACTION_USER_UPDATE;
}
/**
* @return array
*/
public function rules(): array
{
return [
'permissions' => 'required|array',
'permissions.*' => 'string',
];
}
}

View file

@ -140,7 +140,7 @@ abstract class Validable extends Model
} }
return $this->getValidator()->setData( return $this->getValidator()->setData(
$this->getAttributes() $this->toArray()
)->passes(); )->passes();
} }
} }

View file

@ -3,7 +3,6 @@
namespace Pterodactyl\Repositories\Eloquent; namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Subuser;
use Illuminate\Support\Collection;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; 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 * Returns a subuser model for the given user and server combination. If no record
* and permission relationships pre-loaded. * exists an exception will be thrown.
* *
* @param int $server * @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() /** @var \Pterodactyl\Models\Subuser $model */
->with('user', 'permissions') $model = $this->getBuilder()
->where('server_id', $server) ->with('server', 'user')
->get() ->select('subusers.*')
->toBase(); ->join('users', 'users.id', '=', 'subusers.user_id')
->where('subusers.server_id', $server)
->where('users.uuid', $uuid)
->firstOrFail();
return $model;
} }
/** /**

View file

@ -1,63 +0,0 @@
<?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\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);
}
}
}

View file

@ -10,13 +10,12 @@
namespace Pterodactyl\Services\Subusers; namespace Pterodactyl\Services\Subusers;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Subuser;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserCreationService;
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; 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\UserIsServerOwnerException;
use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException; use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException;
@ -25,86 +24,61 @@ class SubuserCreationService
/** /**
* @var \Illuminate\Database\ConnectionInterface * @var \Illuminate\Database\ConnectionInterface
*/ */
protected $connection; private $connection;
/** /**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository
*/ */
protected $keyCreationService; private $subuserRepository;
/**
* @var \Pterodactyl\Services\Subusers\PermissionCreationService
*/
protected $permissionService;
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
*/
protected $subuserRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
/** /**
* @var \Pterodactyl\Services\Users\UserCreationService * @var \Pterodactyl\Services\Users\UserCreationService
*/ */
protected $userCreationService; private $userCreationService;
/** /**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/ */
protected $userRepository; private $userRepository;
/** /**
* SubuserCreationService constructor. * SubuserCreationService constructor.
* *
* @param \Illuminate\Database\ConnectionInterface $connection * @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $subuserRepository
* @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository
* @param \Pterodactyl\Services\Users\UserCreationService $userCreationService * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
*/ */
public function __construct( public function __construct(
ConnectionInterface $connection, ConnectionInterface $connection,
DaemonKeyCreationService $keyCreationService, SubuserRepository $subuserRepository,
PermissionCreationService $permissionService,
ServerRepositoryInterface $serverRepository,
SubuserRepositoryInterface $subuserRepository,
UserCreationService $userCreationService, UserCreationService $userCreationService,
UserRepositoryInterface $userRepository UserRepositoryInterface $userRepository
) { ) {
$this->connection = $connection; $this->connection = $connection;
$this->keyCreationService = $keyCreationService;
$this->permissionService = $permissionService;
$this->serverRepository = $serverRepository;
$this->subuserRepository = $subuserRepository; $this->subuserRepository = $subuserRepository;
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
$this->userCreationService = $userCreationService; $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 string $email
* @param array $permissions * @param array $permissions
* @return \Pterodactyl\Models\Subuser * @return \Pterodactyl\Models\Subuser
* *
* @throws \Exception
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
* @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException * @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) { return $this->connection->transaction(function () use ($server, $email, $permissions) {
$server = $this->serverRepository->find($server);
}
$this->connection->beginTransaction();
try { try {
$user = $this->userRepository->findFirstWhere([['email', '=', $email]]); $user = $this->userRepository->findFirstWhere([['email', '=', $email]]);
@ -117,21 +91,20 @@ class SubuserCreationService
throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists'));
} }
} catch (RecordNotFoundException $exception) { } catch (RecordNotFoundException $exception) {
$username = preg_replace('/([^\w\.-]+)/', '', strtok($email, '@'));
$user = $this->userCreationService->handle([ $user = $this->userCreationService->handle([
'email' => $email, 'email' => $email,
'username' => $username . str_random(3), 'username' => preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')) . str_random(3),
'name_first' => 'Server', 'name_first' => 'Server',
'name_last' => 'Subuser', 'name_last' => 'Subuser',
'root_admin' => false, 'root_admin' => false,
]); ]);
} }
$subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]); return $this->subuserRepository->create([
$this->keyCreationService->handle($server->id, $user->id); 'user_id' => $user->id,
$this->permissionService->handle($subuser->id, $permissions); 'server_id' => $server->id,
$this->connection->commit(); 'permissions' => $permissions,
]);
return $subuser; });
} }
} }

View file

@ -1,35 +0,0 @@
<?php
namespace Pterodactyl\Services\Subusers;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
class SubuserDeletionService
{
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
*/
private $repository;
/**
* SubuserDeletionService constructor.
*
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
*/
public function __construct(
SubuserRepositoryInterface $repository
) {
$this->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);
}
}

View file

@ -1,107 +0,0 @@
<?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\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();
}
}

View file

@ -2,16 +2,10 @@
namespace Pterodactyl\Transformers\Api\Client; namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Subuser;
class SubuserTransformer extends BaseClientTransformer class SubuserTransformer extends BaseClientTransformer
{ {
/**
* @var array
*/
protected $defaultIncludes = ['user'];
/** /**
* Return the resource name for the JSONAPI output. * Return the resource name for the JSONAPI output.
* *
@ -27,23 +21,13 @@ class SubuserTransformer extends BaseClientTransformer
* *
* @param \Pterodactyl\Models\Subuser $model * @param \Pterodactyl\Models\Subuser $model
* @return array|void * @return array|void
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
*/ */
public function transform(Subuser $model) public function transform(Subuser $model)
{ {
return [ return array_merge(
'permissions' => $model->permissions->pluck('permission'), $this->makeTransformer(UserTransformer::class)->transform($model->user),
]; ['permissions' => $model->permissions]
} );
/**
* 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);
} }
} }

View file

@ -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<Subuser> => {
return new Promise((resolve, reject) => {
http.post(`/api/client/servers/${uuid}/users${subuser ? `/${subuser.uuid}` : ''}`, {
...params,
})
.then(data => resolve(rawDataToServerSubuser(data.data)))
.catch(reject);
});
}

View file

@ -8,13 +8,13 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({
image: data.attributes.image, image: data.attributes.image,
twoFactorEnabled: data.attributes['2fa_enabled'], twoFactorEnabled: data.attributes['2fa_enabled'],
createdAt: new Date(data.attributes.created_at), createdAt: new Date(data.attributes.created_at),
permissions: data.attributes.relationships!.permissions.attributes.permissions, permissions: data.attributes.permissions || [],
can: permission => data.attributes.relationships!.permissions.attributes.permissions.indexOf(permission) >= 0, can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0,
}); });
export default (uuid: string): Promise<Subuser[]> => { export default (uuid: string): Promise<Subuser[]> => {
return new Promise((resolve, reject) => { 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))) .then(({ data }) => resolve((data.data || []).map(rawDataToServerSubuser)))
.catch(reject); .catch(reject);
}); });

View file

@ -1,15 +1,19 @@
import React from 'react'; import React, { forwardRef, MutableRefObject, useRef } from 'react';
import { Subuser } from '@/state/server/subusers'; 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 { array, object, string } from 'yup';
import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import Modal, { RequiredModalProps } from '@/components/elements/Modal';
import Field from '@/components/elements/Field'; import Field from '@/components/elements/Field';
import { useStoreState } from 'easy-peasy'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import TitledGreyBox from '@/components/elements/TitledGreyBox'; import TitledGreyBox from '@/components/elements/TitledGreyBox';
import Checkbox from '@/components/elements/Checkbox'; import Checkbox from '@/components/elements/Checkbox';
import styled from 'styled-components'; import styled from 'styled-components';
import classNames from 'classnames'; 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 = { type Props = {
subuser?: Subuser; subuser?: Subuser;
@ -29,13 +33,14 @@ const PermissionLabel = styled.label`
} }
`; `;
const EditSubuserModal = ({ subuser, ...props }: Props) => { const EditSubuserModal = forwardRef<HTMLHeadingElement, Props>(({ subuser, ...props }, ref) => {
const { values, isSubmitting, setFieldValue } = useFormikContext<Values>(); const { values, isSubmitting, setFieldValue } = useFormikContext<Values>();
const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data);
return ( return (
<Modal {...props} showSpinnerOverlay={isSubmitting}> <Modal {...props} showSpinnerOverlay={isSubmitting}>
<h3>{subuser ? 'Edit subuser' : 'Create new subuser'}</h3> <h3 ref={ref}>{subuser ? 'Edit subuser' : 'Create new subuser'}</h3>
<FlashMessageRender byKey={'user:edit'} className={'mt-4'}/>
<div className={'mt-6'}> <div className={'mt-6'}>
<Field <Field
name={'email'} name={'email'}
@ -115,25 +120,48 @@ const EditSubuserModal = ({ subuser, ...props }: Props) => {
</div> </div>
</Modal> </Modal>
); );
}; });
export default (props: Props) => { export default ({ subuser, ...props }: Props) => {
const submit = (values: Values, helpers: FormikHelpers<Values>) => { const ref = useRef<HTMLHeadingElement>(null);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser);
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
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 ( return (
<Formik <Formik
onSubmit={submit} onSubmit={submit}
initialValues={{ initialValues={{
email: '', email: subuser?.email || '',
permissions: [], permissions: subuser?.permissions || [],
} as Values} } as Values}
validationSchema={object().shape({ validationSchema={object().shape({
email: string().email('A valid email address must be provided.').required('A valid email address must be provided.'), email: string().email('A valid email address must be provided.').required('A valid email address must be provided.'),
permissions: array().of(string()), permissions: array().of(string()),
})} })}
> >
<EditSubuserModal {...props}/> <Form>
<EditSubuserModal ref={ref} subuser={subuser} {...props}/>
</Form>
</Formik> </Formik>
); );
}; };

View file

@ -39,7 +39,7 @@ const subusers: ServerSubuserStore = {
}), }),
appendSubuser: action((state, payload) => { 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) => { getSubusers: thunk(async (actions, payload) => {

View file

@ -81,6 +81,10 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
Route::group(['prefix' => '/users'], function () { Route::group(['prefix' => '/users'], function () {
Route::get('/', 'Servers\SubuserController@index'); 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 () { Route::group(['prefix' => '/settings'], function () {