From 4de326ad02511ed416771da4a7a84fc9ee2d76ce Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 16 Sep 2017 18:50:12 -0500 Subject: [PATCH] Add command to disable 2FA for a user account. --- .../Commands/User/DisableTwoFactorCommand.php | 80 ++++++++++++++ app/Console/Kernel.php | 2 + resources/lang/en/command/messages.php | 5 + .../User/DisableTwoFactorCommandTest.php | 104 ++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 app/Console/Commands/User/DisableTwoFactorCommand.php create mode 100644 tests/Unit/Commands/User/DisableTwoFactorCommandTest.php diff --git a/app/Console/Commands/User/DisableTwoFactorCommand.php b/app/Console/Commands/User/DisableTwoFactorCommand.php new file mode 100644 index 000000000..b84ba6fa7 --- /dev/null +++ b/app/Console/Commands/User/DisableTwoFactorCommand.php @@ -0,0 +1,80 @@ +. + * + * 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\Contracts\Repository\UserRepositoryInterface; + +class DisableTwoFactorCommand extends Command +{ + /** + * @var string + */ + protected $description = 'Disable two-factor authentication for a specific user in the Panel.'; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + /** + * @var string + */ + protected $signature = 'p:user:disable2fa {--email= : The email of the user to disable 2-Factor for.}'; + + /** + * DisableTwoFactorCommand constructor. + * + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + */ + public function __construct(UserRepositoryInterface $repository) + { + parent::__construct(); + + $this->repository = $repository; + } + + /** + * Handle command execution process. + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle() + { + if ($this->input->isInteractive()) { + $this->output->warning(trans('command/messages.user.2fa_help_text')); + } + + $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); + $user = $this->repository->withColumns(['id', 'email'])->findFirstWhere([['email', '=', $email]]); + + $this->repository->withoutFresh()->update($user->id, [ + 'use_totp' => false, + 'totp_secret' => null, + ]); + $this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email])); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 1b1ed9b61..0a0ddad81 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -7,6 +7,7 @@ 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\User\DisableTwoFactorCommand; use Pterodactyl\Console\Commands\Location\DeleteLocationCommand; class Kernel extends ConsoleKernel @@ -19,6 +20,7 @@ class Kernel extends ConsoleKernel protected $commands = [ DeleteLocationCommand::class, DeleteUserCommand::class, + DisableTwoFactorCommand::class, MakeLocationCommand::class, MakeUserCommand::class, // \Pterodactyl\Console\Commands\MakeUser::class, diff --git a/resources/lang/en/command/messages.php b/resources/lang/en/command/messages.php index b9fb56cbc..9335b2e5b 100644 --- a/resources/lang/en/command/messages.php +++ b/resources/lang/en/command/messages.php @@ -45,5 +45,10 @@ return [ '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.', + '2fa_help_text' => [ + 'This command will disable 2-factor authentication for a user\'s account if it is enabled. This should only be used as an account recovery command if the user is locked out of their account.', + 'If this is not what you wanted to do, press CTRL+C to exit this process.', + ], + '2fa_disabled' => '2-Factor authentication has been disabled for :email.', ], ]; diff --git a/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php b/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php new file mode 100644 index 000000000..644155b7d --- /dev/null +++ b/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php @@ -0,0 +1,104 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Commands\User; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\User; +use Symfony\Component\Console\Tester\CommandTester; +use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Console\Commands\User\DisableTwoFactorCommand; + +class DisableTwoFactorCommandTest extends TestCase +{ + /** + * @var \Pterodactyl\Console\Commands\User\DisableTwoFactorCommand + */ + protected $command; + + /** + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + */ + protected $repository; + + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(UserRepositoryInterface::class); + + $this->command = new DisableTwoFactorCommand($this->repository); + $this->command->setLaravel($this->app); + } + + /** + * Test 2-factor auth is disabled when no option is passed. + */ + public function testTwoFactorIsDisabledWhenNoOptionIsPassed() + { + $user = factory(User::class)->make(); + + $this->repository->shouldReceive('withColumns')->with(['id', 'email'])->once()->andReturnSelf() + ->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($user->id, [ + 'use_totp' => false, + 'totp_secret' => null, + ])->once()->andReturnNull(); + + $response = new CommandTester($this->command); + $response->setInputs([$user->email]); + $response->execute([]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display); + } + + /** + * Test 2-factor auth is disabled when user is passed in option. + */ + public function testTwoFactorIsDisabledWhenOptionIsPassed() + { + $user = factory(User::class)->make(); + + $this->repository->shouldReceive('withColumns')->with(['id', 'email'])->once()->andReturnSelf() + ->shouldReceive('findFirstWhere')->with([['email', '=', $user->email]])->once()->andReturn($user); + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($user->id, [ + 'use_totp' => false, + 'totp_secret' => null, + ])->once()->andReturnNull(); + + $response = new CommandTester($this->command); + $response->execute([ + '--email' => $user->email, + ]); + + $display = $response->getDisplay(); + $this->assertNotEmpty($display); + $this->assertContains(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display); + } +}