From 542d1f8db7a57933284409682b93efe5c3d93ba6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 15 Sep 2017 22:13:33 -0500 Subject: [PATCH] Add new location and user management via CLI --- .../Location/DeleteLocationCommand.php | 97 ++++++++++++++++ .../MakeLocationCommand.php} | 43 ++++--- .../Commands/User/DeleteUserCommand.php | 108 ++++++++++++++++++ app/Console/Commands/User/MakeUserCommand.php | 78 +++++++++++++ app/Console/Kernel.php | 30 +++-- .../Repository/RepositoryInterface.php | 2 + app/Models/User.php | 10 ++ .../Eloquent/EloquentRepository.php | 10 +- resources/lang/en/admin/user.php | 2 +- resources/lang/en/command/messages.php | 48 ++++++++ 10 files changed, 395 insertions(+), 33 deletions(-) create mode 100644 app/Console/Commands/Location/DeleteLocationCommand.php rename app/Console/Commands/{AddLocation.php => Location/MakeLocationCommand.php} (55%) create mode 100644 app/Console/Commands/User/DeleteUserCommand.php create mode 100644 app/Console/Commands/User/MakeUserCommand.php create mode 100644 resources/lang/en/command/messages.php diff --git a/app/Console/Commands/Location/DeleteLocationCommand.php b/app/Console/Commands/Location/DeleteLocationCommand.php new file mode 100644 index 000000000..d713c233b --- /dev/null +++ b/app/Console/Commands/Location/DeleteLocationCommand.php @@ -0,0 +1,97 @@ +. + * + * 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\Console\Commands\Location; + +use Illuminate\Console\Command; +use Pterodactyl\Services\Locations\LocationDeletionService; +use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; + +class DeleteLocationCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Locations\LocationDeletionService + */ + protected $deletionService; + + /** + * @var \Illuminate\Support\Collection + */ + protected $locations; + + /** + * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:location:delete + {--minimal : Passing this flag will hide the list of current locations.} + {--short= : The short code of the location to delete.}'; + + /** + * DeleteLocationCommand constructor. + * + * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository + * @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService + */ + public function __construct( + LocationDeletionService $deletionService, + LocationRepositoryInterface $repository + ) { + parent::__construct(); + + $this->deletionService = $deletionService; + $this->repository = $repository; + } + + /** + * Respond to the command request. + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException + */ + public function handle() + { + $this->locations = $this->locations ?? $this->repository->getAllWithNodes(); + $short = $this->option('short') ?? $this->anticipate( + trans('command/messages.location.ask_short'), $this->locations->pluck('short')->toArray() + ); + + $location = $this->locations->where('short', $short)->first(); + if (is_null($location)) { + $this->error(trans('command/messages.location.no_location_found')); + if (is_null($this->option('short')) && ! $this->option('no-interaction')) { + $this->handle(); + } + + return; + } + + $this->line(trans('command/messages.location.deleted')); + $this->deletionService->handle($location->id); + } +} diff --git a/app/Console/Commands/AddLocation.php b/app/Console/Commands/Location/MakeLocationCommand.php similarity index 55% rename from app/Console/Commands/AddLocation.php rename to app/Console/Commands/Location/MakeLocationCommand.php index c0a41de20..2b6f59cc7 100644 --- a/app/Console/Commands/AddLocation.php +++ b/app/Console/Commands/Location/MakeLocationCommand.php @@ -22,51 +22,56 @@ * SOFTWARE. */ -namespace Pterodactyl\Console\Commands; +namespace Pterodactyl\Console\Commands\Location; use Illuminate\Console\Command; +use Pterodactyl\Services\Locations\LocationCreationService; -class AddLocation extends Command +class MakeLocationCommand extends Command { - protected $data = []; + /** + * @var \Pterodactyl\Services\Locations\LocationCreationService + */ + protected $creationService; /** - * The name and signature of the console command. - * * @var string */ - protected $signature = 'pterodactyl:location - {--short= : The shortcode name of this location (ex. us1).} - {--long= : A longer description of this location.}'; + protected $signature = 'p:location:make + {--short= : The shortcode name of this location (ex. us1).} + {--long= : A longer description of this location.}'; /** - * The console command description. - * * @var string */ protected $description = 'Creates a new location on the system via the CLI.'; /** * Create a new command instance. + * + * @param \Pterodactyl\Services\Locations\LocationCreationService $creationService */ - public function __construct() + public function __construct(LocationCreationService $creationService) { parent::__construct(); + + $this->creationService = $creationService; } /** - * Execute the console command. + * Handle the command execution process. * - * @return mixed + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function handle() { - $this->data['short'] = (is_null($this->option('short'))) ? $this->ask('Location Short Code') : $this->option('short'); - $this->data['long'] = (is_null($this->option('long'))) ? $this->ask('Location Description') : $this->option('long'); + $short = $this->option('short') ?? $this->ask(trans('command/messages.location.ask_short')); + $long = $this->option('long') ?? $this->ask(trans('command/messages.location.ask_long')); - $repo = new LocationRepository; - $id = $repo->create($this->data); - - $this->info('Location ' . $this->data['short'] . ' created with ID: ' . $id); + $location = $this->creationService->handle(compact('short', 'long')); + $this->line(trans('command/messages.location.created', [ + 'name' => $location->short, + 'id' => $location->id, + ])); } } diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php new file mode 100644 index 000000000..b4c7254d4 --- /dev/null +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -0,0 +1,108 @@ +. + * + * 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\Console\Commands\User; + +use Webmozart\Assert\Assert; +use Illuminate\Console\Command; +use Pterodactyl\Services\Users\UserDeletionService; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; + +class DeleteUserCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Users\UserDeletionService + */ + protected $deletionService; + + /** + * @var string + */ + protected $description = 'Deletes a user from the Panel if no servers are attached to their account.'; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:user:delete {--user=}'; + + public function __construct( + UserDeletionService $deletionService, + UserRepositoryInterface $repository + ) { + parent::__construct(); + + $this->deletionService = $deletionService; + $this->repository = $repository; + } + + /** + * @return bool + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle() + { + $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); + Assert::notEmpty($search, 'Search term must be a non-null value, received %s.'); + + $results = $this->repository->search($search)->all(); + if (count($results) < 1) { + $this->error(trans('command/messages.user.no_users_found')); + if (! $this->option('no-interaction')) { + return $this->handle(); + } + + return false; + } + + if (! $this->option('no-interaction')) { + $tableValues = []; + foreach ($results as $user) { + $tableValues[] = [$user->id, $user->email, $user->name]; + } + + $this->table(['User ID', 'Email', 'Name'], $tableValues); + if (! $deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) { + return $this->handle(); + } + } else { + if (count($results) > 1) { + $this->error(trans('command/messages.user.multiple_found')); + + return false; + } + + $deleteUser = $results->first(); + } + + if ($this->confirm(trans('command/messages.user.confirm_delete')) || $this->option('no-interaction')) { + $this->deletionService->handle($deleteUser); + $this->info(trans('command/messages.user.deleted')); + } + } +} diff --git a/app/Console/Commands/User/MakeUserCommand.php b/app/Console/Commands/User/MakeUserCommand.php new file mode 100644 index 000000000..f98517e41 --- /dev/null +++ b/app/Console/Commands/User/MakeUserCommand.php @@ -0,0 +1,78 @@ +. + * + * 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\Console\Commands\User; + +use Illuminate\Console\Command; +use Pterodactyl\Services\Users\UserCreationService; + +class MakeUserCommand extends Command +{ + /** + * @var \Pterodactyl\Services\Users\UserCreationService + */ + protected $creationService; + + protected $signature = 'p:user:make {--email=} {--username=} {--name-first=} {--name-last=} {--password=} {--no-password}'; + + /** + * MakeUserCommand constructor. + * + * @param \Pterodactyl\Services\Users\UserCreationService $creationService + */ + public function __construct(UserCreationService $creationService) + { + parent::__construct(); + + $this->creationService = $creationService; + } + + /** + * Handle command request to create a new user. + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle() + { + $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); + $username = $this->option('username') ?? $this->ask(trans('command/messages.user.ask_username')); + $name_first = $this->option('name-first') ?? $this->ask(trans('command/messages.user.ask_name_first')); + $name_last = $this->option('name-last') ?? $this->ask(trans('command/messages.user.ask_name_last')); + + if (is_null($password = $this->option('password')) && ! $this->option('no-password')) { + $this->warn(trans('command/messages.user.ask_password_help')); + $this->line(trans('command/messages.user.ask_password_tip')); + $password = $this->secret(trans('command/messages.user.ask_password')); + } + + $user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password')); + $this->table(['Field', 'Value'], [ + ['UUID', $user->uuid], + ['Email', $user->email], + ['Username', $user->username], + ['Name', $user->name], + ]); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 233e03ea5..1b1ed9b61 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -3,7 +3,11 @@ namespace Pterodactyl\Console; use Illuminate\Console\Scheduling\Schedule; +use Pterodactyl\Console\Commands\User\MakeUserCommand; +use Pterodactyl\Console\Commands\User\DeleteUserCommand; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Pterodactyl\Console\Commands\Location\MakeLocationCommand; +use Pterodactyl\Console\Commands\Location\DeleteLocationCommand; class Kernel extends ConsoleKernel { @@ -13,17 +17,21 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ - \Pterodactyl\Console\Commands\MakeUser::class, - \Pterodactyl\Console\Commands\ShowVersion::class, - \Pterodactyl\Console\Commands\UpdateEnvironment::class, - \Pterodactyl\Console\Commands\RunTasks::class, - \Pterodactyl\Console\Commands\ClearTasks::class, - \Pterodactyl\Console\Commands\ClearServices::class, - \Pterodactyl\Console\Commands\UpdateEmailSettings::class, - \Pterodactyl\Console\Commands\CleanServiceBackup::class, - \Pterodactyl\Console\Commands\AddNode::class, - \Pterodactyl\Console\Commands\AddLocation::class, - \Pterodactyl\Console\Commands\RebuildServer::class, + DeleteLocationCommand::class, + DeleteUserCommand::class, + MakeLocationCommand::class, + MakeUserCommand::class, +// \Pterodactyl\Console\Commands\MakeUser::class, +// \Pterodactyl\Console\Commands\ShowVersion::class, +// \Pterodactyl\Console\Commands\UpdateEnvironment::class, +// \Pterodactyl\Console\Commands\RunTasks::class, +// \Pterodactyl\Console\Commands\ClearTasks::class, +// \Pterodactyl\Console\Commands\ClearServices::class, +// \Pterodactyl\Console\Commands\UpdateEmailSettings::class, +// \Pterodactyl\Console\Commands\CleanServiceBackup::class, +// \Pterodactyl\Console\Commands\AddNode::class, +// \Pterodactyl\Console\Commands\MakeLocationCommand::class, +// \Pterodactyl\Console\Commands\RebuildServer::class, ]; /** diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php index aa8154fbd..172508d79 100644 --- a/app/Contracts/Repository/RepositoryInterface.php +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -120,6 +120,8 @@ interface RepositoryInterface * * @param array $fields * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function findFirstWhere(array $fields); diff --git a/app/Models/User.php b/app/Models/User.php index 29941e090..40ca19d2f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -300,6 +300,16 @@ class User extends Model implements $this->attributes['username'] = strtolower($value); } + /** + * Return a concated result for the accounts full name. + * + * @return string + */ + public function getNameAttribute() + { + return $this->name_first . ' ' . $this->name_last; + } + /** * Returns all permissions that a user has. * diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index f29418d00..73096314c 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -30,6 +30,7 @@ use Illuminate\Database\Query\Expression; use Pterodactyl\Contracts\Repository\RepositoryInterface; use Pterodactyl\Exceptions\Model\DataValidationException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; abstract class EloquentRepository extends Repository implements RepositoryInterface { @@ -106,7 +107,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf $instance = $this->getBuilder()->where($fields)->first($this->getColumns()); if (! $instance) { - throw new RecordNotFoundException(); + throw new RecordNotFoundException; } return $instance; @@ -200,7 +201,12 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf */ public function all() { - return $this->getBuilder()->get($this->getColumns()); + $instance = $this->getBuilder(); + if (interface_exists(SearchableInterface::class)) { + $instance = $instance->search($this->searchTerm); + } + + return $instance->get($this->getColumns()); } /** diff --git a/resources/lang/en/admin/user.php b/resources/lang/en/admin/user.php index b8d38d323..c83c9ba66 100644 --- a/resources/lang/en/admin/user.php +++ b/resources/lang/en/admin/user.php @@ -24,7 +24,7 @@ return [ 'exceptions' => [ - 'user_has_servers' => 'Cannot delete a user with active servers attached to their account. Please delete their server\'s before continuing.', + 'user_has_servers' => 'Cannot delete a user with active servers attached to their account. Please delete their servers before continuing.', ], 'notices' => [ 'account_created' => 'Account has been created successfully.', diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php new file mode 100644 index 000000000..b6bb40fe6 --- /dev/null +++ b/resources/lang/en/command/messages.php @@ -0,0 +1,48 @@ +. + * + * 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. + */ + +return [ + 'location' => [ + 'no_location_found' => 'Could not locate a record matching the provided short code.', + 'ask_short' => 'Location Short Code', + 'ask_long' => 'Location Description', + 'created' => 'Successfully created a new location (:name) with an ID of :id.', + 'deleted' => 'Successfully deleted the requested location.', + ], + 'user' => [ + 'search_users' => 'Enter a Username, UUID, or Email Address', + 'select_search_user' => 'ID of user to delete (Enter \'0\' to re-search)', + 'deleted' => 'User successfully deleted from the Panel.', + 'confirm_delete' => 'Are you sure you want to delete this user from the Panel?', + 'no_users_found' => 'No users were found for the search term provided.', + 'multiple_found' => 'Multiple accounts were found for the user provided, unable to delete a user because of the --no-interaction flag.', + 'ask_email' => 'Email Address', + 'ask_username' => 'Username', + 'ask_name_first' => 'First Name', + 'ask_name_last' => 'Last Name', + 'ask_password' => 'Password', + 'ask_password_tip' => 'If you would like to create an account with a random password emailed to the user, re-run this command (CTRL+C) and pass the `--no-password` flag.', + 'ask_password_help' => 'Passwords must be at least 8 characters in length and contain at least one capital letter and number.', + ], +];