168 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace Pterodactyl\Tests\Integration\Api\Client;
 | |
| 
 | |
| use Carbon\Carbon;
 | |
| use Pterodactyl\Models\User;
 | |
| use Illuminate\Http\Response;
 | |
| use PragmaRX\Google2FA\Google2FA;
 | |
| use Pterodactyl\Models\RecoveryToken;
 | |
| use PHPUnit\Framework\ExpectationFailedException;
 | |
| 
 | |
| class TwoFactorControllerTest extends ClientApiIntegrationTestCase
 | |
| {
 | |
|     /**
 | |
|      * Test that image data for enabling 2FA is returned by the endpoint and that the user
 | |
|      * record in the database is updated as expected.
 | |
|      */
 | |
|     public function testTwoFactorImageDataIsReturned()
 | |
|     {
 | |
|         /** @var \Pterodactyl\Models\User $user */
 | |
|         $user = factory(User::class)->create(['use_totp' => false]);
 | |
| 
 | |
|         $this->assertFalse($user->use_totp);
 | |
|         $this->assertEmpty($user->totp_secret);
 | |
|         $this->assertEmpty($user->totp_authenticated_at);
 | |
| 
 | |
|         $response = $this->actingAs($user)->getJson('/api/client/account/two-factor');
 | |
| 
 | |
|         $response->assertOk();
 | |
|         $response->assertJsonStructure(['data' => ['image_url_data']]);
 | |
| 
 | |
|         $user = $user->refresh();
 | |
| 
 | |
|         $this->assertFalse($user->use_totp);
 | |
|         $this->assertNotEmpty($user->totp_secret);
 | |
|         $this->assertEmpty($user->totp_authenticated_at);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Test that an error is returned if the user's account already has 2FA enabled on it.
 | |
|      */
 | |
|     public function testErrorIsReturnedWhenTwoFactorIsAlreadyEnabled()
 | |
|     {
 | |
|         /** @var \Pterodactyl\Models\User $user */
 | |
|         $user = factory(User::class)->create(['use_totp' => true]);
 | |
| 
 | |
|         $response = $this->actingAs($user)->getJson('/api/client/account/two-factor');
 | |
| 
 | |
|         $response->assertStatus(Response::HTTP_BAD_REQUEST);
 | |
|         $response->assertJsonPath('errors.0.code', 'BadRequestHttpException');
 | |
|         $response->assertJsonPath('errors.0.detail', 'Two-factor authentication is already enabled on this account.');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Test that a validation error is thrown if invalid data is passed to the 2FA endpoint.
 | |
|      */
 | |
|     public function testValidationErrorIsReturnedIfInvalidDataIsPassedToEnabled2FA()
 | |
|     {
 | |
|         /** @var \Pterodactyl\Models\User $user */
 | |
|         $user = factory(User::class)->create(['use_totp' => false]);
 | |
| 
 | |
|         $response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
 | |
|             'code' => '',
 | |
|         ]);
 | |
| 
 | |
|         $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
 | |
|         $response->assertJsonPath('errors.0.code', 'required');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Tests that 2FA can be enabled on an account for the user.
 | |
|      */
 | |
|     public function testTwoFactorCanBeEnabledOnAccount()
 | |
|     {
 | |
|         /** @var \Pterodactyl\Models\User $user */
 | |
|         $user = factory(User::class)->create(['use_totp' => false]);
 | |
| 
 | |
|         // Make the initial call to get the account setup for 2FA.
 | |
|         $this->actingAs($user)->getJson('/api/client/account/two-factor')->assertOk();
 | |
| 
 | |
|         $user = $user->refresh();
 | |
|         $this->assertNotNull($user->totp_secret);
 | |
| 
 | |
|         /** @var \PragmaRX\Google2FA\Google2FA $service */
 | |
|         $service = $this->app->make(Google2FA::class);
 | |
| 
 | |
|         $secret = decrypt($user->totp_secret);
 | |
|         $token = $service->getCurrentOtp($secret);
 | |
| 
 | |
|         $response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
 | |
|             'code' => $token,
 | |
|         ]);
 | |
| 
 | |
|         $response->assertOk();
 | |
|         $response->assertJsonPath('object', 'recovery_tokens');
 | |
| 
 | |
|         $user = $user->refresh();
 | |
|         $this->assertTrue($user->use_totp);
 | |
| 
 | |
|         $tokens = RecoveryToken::query()->where('user_id', $user->id)->get();
 | |
|         $this->assertCount(10, $tokens);
 | |
|         $this->assertStringStartsWith('$2y$10$', $tokens[0]->token);
 | |
| 
 | |
|         $tokens = $tokens->pluck('token')->toArray();
 | |
| 
 | |
|         foreach ($response->json('attributes.tokens') as $raw) {
 | |
|             foreach ($tokens as $hashed) {
 | |
|                 if (password_verify($raw, $hashed)) {
 | |
|                     continue 2;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             throw new ExpectationFailedException(
 | |
|                 sprintf('Failed asserting that token [%s] exists as a hashed value in recovery_tokens table.', $raw)
 | |
|             );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Test that two factor authentication can be disabled on an account as long as the password
 | |
|      * provided is valid for the account.
 | |
|      */
 | |
|     public function testTwoFactorCanBeDisabledOnAccount()
 | |
|     {
 | |
|         Carbon::setTestNow(Carbon::now());
 | |
| 
 | |
|         /** @var \Pterodactyl\Models\User $user */
 | |
|         $user = factory(User::class)->create(['use_totp' => true]);
 | |
| 
 | |
|         $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
 | |
|             'password' => 'invalid',
 | |
|         ]);
 | |
| 
 | |
|         $response->assertStatus(Response::HTTP_BAD_REQUEST);
 | |
|         $response->assertJsonPath('errors.0.code', 'BadRequestHttpException');
 | |
|         $response->assertJsonPath('errors.0.detail', 'The password provided was not valid.');
 | |
| 
 | |
|         $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
 | |
|             'password' => 'password',
 | |
|         ]);
 | |
| 
 | |
|         $response->assertStatus(Response::HTTP_NO_CONTENT);
 | |
| 
 | |
|         $user = $user->refresh();
 | |
|         $this->assertFalse($user->use_totp);
 | |
|         $this->assertNotNull($user->totp_authenticated_at);
 | |
|         $this->assertSame(Carbon::now()->toIso8601String(), $user->totp_authenticated_at->toIso8601String());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Test that no error is returned when trying to disabled two factor on an account where it
 | |
|      * was not enabled in the first place.
 | |
|      */
 | |
|     public function testNoErrorIsReturnedIfTwoFactorIsNotEnabled()
 | |
|     {
 | |
|         Carbon::setTestNow(Carbon::now());
 | |
| 
 | |
|         /** @var \Pterodactyl\Models\User $user */
 | |
|         $user = factory(User::class)->create(['use_totp' => false]);
 | |
| 
 | |
|         $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
 | |
|             'password' => 'password',
 | |
|         ]);
 | |
| 
 | |
|         $response->assertStatus(Response::HTTP_NO_CONTENT);
 | |
|     }
 | |
| }
 | 
