Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add downloaded field to recoverycode #23

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Entity/RecoveryCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class RecoveryCode
*/
private string $secret;

/**
* @ORM\Column(type="boolean")
*/
private bool $downloaded = false;

public function getId(): int
{
return $this->id;
Expand All @@ -61,4 +66,14 @@ public function setSecret(string $secret): void
{
$this->secret = $secret;
}

public function isDownloaded(): bool
{
return $this->downloaded;
}

public function setDownloaded(bool $downloaded): void
{
$this->downloaded = $downloaded;
}
}
58 changes: 58 additions & 0 deletions src/Handler/DownloadedRecoveryCodeHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/*
* This file is part of the Connect Holland Secure JWT package and distributed under the terms of the MIT License.
* Copyright (c) 2020 Connect Holland.
*/

namespace ConnectHolland\SecureJWTBundle\Handler;

use ConnectHolland\SecureJWTBundle\Entity\RecoveryCode;
use ConnectHolland\SecureJWTBundle\Entity\TwoFactorUserInterface;
use ConnectHolland\SecureJWTBundle\Message\DownloadedRecoveryCode;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class DownloadedRecoveryCodeHandler implements MessageHandlerInterface
{
private ManagerRegistry $doctrine;

private TokenStorageInterface $tokenStorage;

/**
* RecoveryCodeHandler constructor.
*/
public function __construct(ManagerRegistry $doctrine, TokenStorageInterface $tokenStorage)
{
$this->doctrine = $doctrine;
$this->tokenStorage = $tokenStorage;
}

/**
* Set downloaded to true on recovery code(s) for logged on user.
*/
public function __invoke(DownloadedRecoveryCode $recoveryCode): void
{
$user = $this->tokenStorage->getToken()->getUser();

if ($user instanceof TwoFactorUserInterface) {
$this->setDownloaded($user->getGoogleAuthenticatorSecret(), $recoveryCode->getValue());
} else {
throw new \RuntimeException('Unable to set downloaded on recovery codes for non-2fa users');
}
}

/**
* Set downloaded to true on recovery code(s) for the given User.
*/
private function setDownloaded(string $secret, bool $value): void
{
$currentCodes = $this->doctrine->getRepository(RecoveryCode::class)->findBy(['secret' => $secret]);
$manager = $this->doctrine->getManagerForClass(RecoveryCode::class);

array_walk($currentCodes, fn (RecoveryCode $recoveryCode) => $recoveryCode->setDownloaded($value));

$manager->flush();
}
}
38 changes: 38 additions & 0 deletions src/Message/DownloadedRecoveryCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of the Connect Holland Secure JWT package and distributed under the terms of the MIT License.
* Copyright (c) 2020 Connect Holland.
*/

namespace ConnectHolland\SecureJWTBundle\Message;

use ApiPlatform\Core\Annotation\ApiResource;

/**
* Create recovery codes for the current user. Will invalidate any existing codes for that user.
*
* @ApiResource(
* messenger=true,
* collectionOperations={},
* itemOperations={
* "post"={"status"=200}
* },
* )
*
* @codeCoverageIgnore Trivial class with only a getter
*/
class DownloadedRecoveryCode
{
private bool $value;

public function __construct(bool $value)
{
$this->value = $value;
}

public function getValue(): bool
{
return $this->value;
}
}
61 changes: 61 additions & 0 deletions tests/Handler/DownloadedRecoveryCodeHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/*
* This file is part of the Connect Holland Secure JWT package and distributed under the terms of the MIT License.
* Copyright (c) 2020 Connect Holland.
*/

namespace ConnectHolland\SecureJWTBundle\Tests\Handler;

use ConnectHolland\SecureJWTBundle\Entity\RecoveryCode as RecoveryCodeEntity;
use ConnectHolland\SecureJWTBundle\Handler\DownloadedRecoveryCodeHandler;
use ConnectHolland\SecureJWTBundle\Message\DownloadedRecoveryCode;
use ConnectHolland\SecureJWTBundle\Tests\Fixture\User;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;

class DownloadedRecoveryCodeHandlerTest extends TestCase
{
public function testSetCodesDownloadedSuccessful(): void
arnorietdijk marked this conversation as resolved.
Show resolved Hide resolved
{
$doctrine = $this->createMock(ManagerRegistry::class);
$manager = $this->createMock(EntityManager::class);
$repository = $this->createMock(EntityRepository::class);
$tokenStorage = new TokenStorage();
$user = new User();
$recoveryCodes = [
new RecoveryCodeEntity(),
new RecoveryCodeEntity(),
new RecoveryCodeEntity()
];

$user->setGoogleAuthenticatorSecret('verysecret!');
$tokenStorage->setToken(new JWTUserToken([], $user));

$repository
->method('findBy')
->willReturn($recoveryCodes);

$manager
->expects($this->exactly(1))
->method('flush');

$doctrine
->method('getManagerForClass')
->willReturn($manager);

$doctrine
->method('getRepository')
->willReturn($repository);

$handler = new DownloadedRecoveryCodeHandler($doctrine, $tokenStorage);
$handler(new DownloadedRecoveryCode(true));
foreach ($recoveryCodes as $code) {
$this->assertSame(true, $code->isDownloaded());
}
}
}