1
0
Fork 0
mirror of https://github.com/wallabag/wallabag.git synced 2025-08-11 17:51:02 +00:00

Add a two-step setup of OTP

Before this change, 2FA with OTP was enabled before the user was able to
submit a code to validate the setup. Thus, this could lead to a
situation where the user is locked out of her account if there was an
issue setting up her application.

Now we rely on a new boolean property that is set to true only after the
user submits a valid code during the setup phase.

Fixes #4867

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
This commit is contained in:
Kevin Decherf 2025-04-13 16:12:13 +02:00
parent 3f6c01103d
commit b09224cac1
6 changed files with 125 additions and 17 deletions

View file

@ -3,6 +3,7 @@
namespace Tests\Wallabag\Controller;
use Doctrine\ORM\EntityManagerInterface;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
@ -1223,27 +1224,74 @@ class ConfigControllerTest extends WallabagTestCase
public function testUserEnable2faGoogle()
{
$googleAuthenticatorMock = $this->createMock(GoogleAuthenticatorInterface::class);
$googleAuthenticatorMock
->method('generateSecret')
->willReturn('DUMMYSECRET');
$googleAuthenticatorMock
->method('checkCode')
->willReturnCallback(function ($user, $code) {
return '123456' === $code;
});
$this->logInAs('admin');
$client = $this->getTestClient();
$client->disableReboot(); // Disable reboot to keep the mock in the container
// name::class notation does not work in this context
self::getContainer()->set('scheb_two_factor.security.google_authenticator', $googleAuthenticatorMock);
$crawler = $client->request('GET', '/config');
$form = $crawler->filter('form[name=config_otp_app]')->form();
$client->submit($form);
$crawler = $client->submit($form);
$this->assertSame(200, $client->getResponse()->getStatusCode());
// restore user
$secret = $crawler->filter('div#config_otp_app_secret pre code')->innerText();
$this->assertSame('DUMMYSECRET', $secret);
$em = $this->getEntityManager();
$user = $em
->getRepository(User::class)
->findOneByUsername('admin');
// At this phase, the user should not have 2FA enabled
$this->assertFalse($user->isGoogleTwoFactor());
// First test: send invalid OTP code
$form = $crawler->filter('form[name=config_otp_app_check]')->form();
$data = [
'_auth_code' => '000000',
];
$client->submit($form, $data);
$this->assertSame(307, $client->getResponse()->getStatusCode());
$this->assertStringContainsString('flashes.config.notice.otp_code_invalid', $client->getContainer()->get(SessionInterface::class)->getFlashBag()->get('notice')[0]);
// Follow the redirect to the OTP check form again
$crawler = $client->followRedirect();
// Second test: send valid OTP code
$form = $crawler->filter('form[name=config_otp_app_check]')->form();
$data = [
'_auth_code' => '123456',
];
$client->submit($form, $data);
$this->assertSame(302, $client->getResponse()->getStatusCode());
$this->assertStringContainsString('flashes.config.notice.otp_enabled', $client->getContainer()->get(SessionInterface::class)->getFlashBag()->get('notice')[0]);
$em = $this->getEntityManager();
$user = $em
->getRepository(User::class)
->findOneByUsername('admin');
$this->assertTrue($user->isGoogleTwoFactor());
$this->assertGreaterThan(0, $user->getBackupCodes());
$this->assertGreaterThan(0, \count($user->getBackupCodes()));
$user->setGoogleAuthenticatorSecret(false);
$user->setBackupCodes(null);
// Restore user
$user->setGoogleAuthenticatorSecret('');
$user->setGoogleAuthenticator(false);
$user->setBackupCodes([]);
$em->persist($user);
$em->flush();
}
@ -1259,6 +1307,7 @@ class ConfigControllerTest extends WallabagTestCase
->findOneByUsername('admin');
$user->setGoogleAuthenticatorSecret('Google2FA');
$user->setGoogleAuthenticator(true);
$em->persist($user);
$em->flush();
@ -1271,7 +1320,6 @@ class ConfigControllerTest extends WallabagTestCase
$this->assertStringContainsString('flashes.config.notice.otp_disabled', $client->getContainer()->get(SessionInterface::class)->getFlashBag()->get('notice')[0]);
// restore user
$em = $this->getEntityManager();
$user = $em
->getRepository(User::class)
@ -1279,6 +1327,7 @@ class ConfigControllerTest extends WallabagTestCase
$this->assertEmpty($user->getGoogleAuthenticatorSecret());
$this->assertEmpty($user->getBackupCodes());
$this->assertFalse($user->isGoogleTwoFactor());
}
public function testExportTaggingRule()