Skip to content
This repository was archived by the owner on Dec 2, 2021. It is now read-only.

Commit b6f7a12

Browse files
committed
Extract code into TrustedDeviceTokenEncoder
(cherry picked from commit 0c35148e3651c63abfaecbfc2724e1a8da599e96)
1 parent e08bbaf commit b6f7a12

File tree

6 files changed

+218
-86
lines changed

6 files changed

+218
-86
lines changed

Resources/config/trusted_device.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
<argument>%kernel.secret%</argument>
66
</service>
77

8+
<service id="scheb_two_factor.trusted_token_encoder" class="Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedDeviceTokenEncoder">
9+
<argument type="service" id="scheb_two_factor.trusted_jwt_encoder" />
10+
<argument>%scheb_two_factor.trusted_device.lifetime%</argument>
11+
</service>
12+
813
<service id="scheb_two_factor.trusted_token_storage" class="Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedDeviceTokenStorage" lazy="true">
914
<argument type="service" id="request_stack" />
10-
<argument type="service" id="scheb_two_factor.trusted_jwt_encoder" />
15+
<argument type="service" id="scheb_two_factor.trusted_token_encoder" />
1116
<argument>%scheb_two_factor.trusted_device.cookie_name%</argument>
12-
<argument>%scheb_two_factor.trusted_device.lifetime%</argument>
1317
</service>
1418

1519
<service id="scheb_two_factor.trusted_cookie_response_listener" class="Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedCookieResponseListener" lazy="true">

Security/TwoFactor/Trusted/TrustedDeviceToken.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public function versionMatches(int $version): bool
2929
return $this->jwtToken->getClaim(JwtTokenEncoder::CLAIM_VERSION, false) === $version;
3030
}
3131

32+
public function isExpired(): bool
33+
{
34+
return $this->jwtToken->isExpired();
35+
}
36+
3237
public function serialize(): string
3338
{
3439
return (string) $this->jwtToken;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Scheb\TwoFactorBundle\Security\TwoFactor\Trusted;
6+
7+
class TrustedDeviceTokenEncoder
8+
{
9+
/**
10+
* @var JwtTokenEncoder
11+
*/
12+
private $jwtTokenEncoder;
13+
14+
/**
15+
* @var int
16+
*/
17+
private $trustedTokenLifetime;
18+
19+
public function __construct(JwtTokenEncoder $jwtTokenEncoder, int $trustedTokenLifetime)
20+
{
21+
$this->jwtTokenEncoder = $jwtTokenEncoder;
22+
$this->trustedTokenLifetime = $trustedTokenLifetime;
23+
}
24+
25+
public function generateToken(string $username, string $firewall, int $version): TrustedDeviceToken
26+
{
27+
$validUntil = $this->getValidUntil();
28+
$jwtToken = $this->jwtTokenEncoder->generateToken($username, $firewall, $version, $validUntil);
29+
30+
return new TrustedDeviceToken($jwtToken);
31+
}
32+
33+
public function decodeToken(string $trustedTokenEncoded): ?TrustedDeviceToken
34+
{
35+
$jwtToken = $this->jwtTokenEncoder->decodeToken($trustedTokenEncoded);
36+
if (null === $jwtToken) {
37+
return null;
38+
}
39+
40+
return new TrustedDeviceToken($jwtToken);
41+
}
42+
43+
private function getValidUntil(): \DateTimeInterface
44+
{
45+
return $this->getDateTimeNow()->add(new \DateInterval('PT'.$this->trustedTokenLifetime.'S'));
46+
}
47+
48+
protected function getDateTimeNow(): \DateTimeImmutable
49+
{
50+
return new \DateTimeImmutable();
51+
}
52+
}

Security/TwoFactor/Trusted/TrustedDeviceTokenStorage.php

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,15 @@ class TrustedDeviceTokenStorage
1717
private $requestStack;
1818

1919
/**
20-
* @var JwtTokenEncoder
20+
* @var TrustedDeviceTokenEncoder
2121
*/
22-
private $jwtTokenEncoder;
22+
private $tokenGenerator;
2323

2424
/**
2525
* @var string
2626
*/
2727
private $cookieName;
2828

29-
/**
30-
* @var int
31-
*/
32-
private $trustedTokenLifetime;
33-
3429
/**
3530
* @var TrustedDeviceToken[]|null
3631
*/
@@ -41,12 +36,11 @@ class TrustedDeviceTokenStorage
4136
*/
4237
private $updateCookie = false;
4338

44-
public function __construct(RequestStack $requestStack, JwtTokenEncoder $jwtTokenEncoder, string $cookieName, int $trustedTokenLifetime)
39+
public function __construct(RequestStack $requestStack, TrustedDeviceTokenEncoder $tokenGenerator, string $cookieName)
4540
{
46-
$this->jwtTokenEncoder = $jwtTokenEncoder;
4741
$this->requestStack = $requestStack;
42+
$this->tokenGenerator = $tokenGenerator;
4843
$this->cookieName = $cookieName;
49-
$this->trustedTokenLifetime = $trustedTokenLifetime;
5044
}
5145

5246
public function hasUpdatedCookie(): bool
@@ -89,22 +83,10 @@ public function addTrustedToken(string $username, string $firewall, int $version
8983
}
9084
}
9185

92-
$validUntil = $this->getValidUntil();
93-
$jwtToken = $this->jwtTokenEncoder->generateToken($username, $firewall, $version, $validUntil);
94-
$this->trustedTokenList[] = new TrustedDeviceToken($jwtToken);
86+
$this->trustedTokenList[] = $this->tokenGenerator->generateToken($username, $firewall, $version);
9587
$this->updateCookie = true;
9688
}
9789

98-
private function getValidUntil(): \DateTimeInterface
99-
{
100-
return $this->getDateTimeNow()->add(new \DateInterval('PT'.$this->trustedTokenLifetime.'S'));
101-
}
102-
103-
protected function getDateTimeNow(): \DateTimeImmutable
104-
{
105-
return new \DateTimeImmutable();
106-
}
107-
10890
/**
10991
* @return TrustedDeviceToken[]
11092
*/
@@ -130,11 +112,11 @@ private function readTrustedTokenList(): array
130112
$trustedTokenList = [];
131113
$trustedTokenEncodedList = explode(self::TOKEN_DELIMITER, $cookie);
132114
foreach ($trustedTokenEncodedList as $trustedTokenEncoded) {
133-
$trustedToken = $this->jwtTokenEncoder->decodeToken($trustedTokenEncoded);
115+
$trustedToken = $this->tokenGenerator->decodeToken($trustedTokenEncoded);
134116
if (!$trustedToken || $trustedToken->isExpired()) {
135117
$this->updateCookie = true; // When there are invalid token, update the cookie to remove them
136118
} else {
137-
$trustedTokenList[] = new TrustedDeviceToken($trustedToken);
119+
$trustedTokenList[] = $trustedToken;
138120
}
139121
}
140122

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Scheb\TwoFactorBundle\Tests\Security\TwoFactor\Trusted;
6+
7+
use Lcobucci\JWT\Token;
8+
use PHPUnit\Framework\MockObject\MockObject;
9+
use Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\JwtTokenEncoder;
10+
use Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedDeviceToken;
11+
use Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedDeviceTokenEncoder;
12+
use Scheb\TwoFactorBundle\Tests\TestCase;
13+
14+
class TrustedDeviceTokenEncoderTest extends TestCase
15+
{
16+
/**
17+
* @var MockObject|JwtTokenEncoder
18+
*/
19+
private $jwtEncoder;
20+
21+
/**
22+
* @var TestableTrustedDeviceTokenEncoder
23+
*/
24+
private $tokenEncoder;
25+
26+
protected function setUp(): void
27+
{
28+
$this->jwtEncoder = $this->createMock(JwtTokenEncoder::class);
29+
$this->tokenEncoder = new TestableTrustedDeviceTokenEncoder($this->jwtEncoder, 3600);
30+
$this->tokenEncoder->now = new \DateTimeImmutable('2018-01-01 00:00:00');
31+
}
32+
33+
/**
34+
* @test
35+
*/
36+
public function generateToken_parametersGiven_returnTrustedDeviceToken(): void
37+
{
38+
$this->jwtEncoder
39+
->expects($this->once())
40+
->method('generateToken')
41+
->with('username', 'firewallName', 1, new \DateTime('2018-01-01 01:00:00'));
42+
43+
$token = $this->tokenEncoder->generateToken('username', 'firewallName', 1);
44+
$this->assertInstanceOf(TrustedDeviceToken::class, $token);
45+
}
46+
47+
/**
48+
* @test
49+
*/
50+
public function decodeToken_validToken_returnDecodedTrustedDeviceToken(): void
51+
{
52+
$this->jwtEncoder
53+
->expects($this->once())
54+
->method('decodeToken')
55+
->willReturn($this->createMock(Token::class));
56+
57+
$returnValue = $this->tokenEncoder->decodeToken('validToken');
58+
$this->assertInstanceOf(TrustedDeviceToken::class, $returnValue);
59+
}
60+
61+
/**
62+
* @test
63+
*/
64+
public function decodeToken_invalidToken_returnNull(): void
65+
{
66+
$this->jwtEncoder
67+
->expects($this->once())
68+
->method('decodeToken')
69+
->willReturn(null);
70+
71+
$returnValue = $this->tokenEncoder->decodeToken('invalidToken');
72+
$this->assertNull($returnValue);
73+
}
74+
}
75+
76+
// Make the current DateTime testable
77+
class TestableTrustedDeviceTokenEncoder extends TrustedDeviceTokenEncoder
78+
{
79+
public $now;
80+
81+
protected function getDateTimeNow(): \DateTimeImmutable
82+
{
83+
return $this->now;
84+
}
85+
}

0 commit comments

Comments
 (0)