Skip to content

Commit

Permalink
bug #43 improve signature expiration support (jrushlow)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the master branch.

Discussion
----------

improve signature expiration support

This PR resolves the long standing issue of possibly invalid expiration values. It all adds support for translating when a signature expires out of the box.

closes #39

Commits
-------

e2e39ed improve signature expiration support
  • Loading branch information
weaverryan committed Dec 18, 2020
2 parents 8523b0f + e2e39ed commit fe19842
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ find a change that break's semver, please create an issue.*

## NEXT

* Add translation support for signature expiration time
* Fixed invalid signature expiration time

## v1.1.0

* Added PHP 8 support - see #42 thanks to @ker0x!
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"symfony/config": "^4.4 | ^5.0",
"symfony/dependency-injection": "^4.4 | ^5.0",
"symfony/http-kernel": "^4.4 | ^5.0",
"symfony/routing": "^4.4 | ^5.0"
"symfony/routing": "^4.4 | ^5.0",
"symfony/deprecation-contracts": "^2.2"
},
"require-dev": {
"doctrine/orm": "^2.7",
Expand Down
95 changes: 94 additions & 1 deletion src/Model/VerifyEmailSignatureComponents.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,25 @@ final class VerifyEmailSignatureComponents
*/
private $uri;

public function __construct(\DateTimeInterface $expiresAt, string $uri)
/**
* @var int|null timestamp when the signature was created
*/
private $generatedAt;

/**
* @var int expiresAt translator interval
*/
private $transInterval = 0;

public function __construct(\DateTimeInterface $expiresAt, string $uri, int $generatedAt = null)
{
$this->expiresAt = $expiresAt;
$this->uri = $uri;
$this->generatedAt = $generatedAt;

if (null === $generatedAt) {
$this->triggerDeprecation();
}
}

/**
Expand All @@ -46,4 +61,82 @@ public function getExpiresAt(): \DateTimeInterface
{
return $this->expiresAt;
}

/**
* Get the translation message for when a signature expires.
*
* This is used in conjunction with the getExpirationMessageData() method.
* Example usage in a Twig template:
*
* <p>{{ components.expirationMessageKey|trans(components.expirationMessageData) }}</p>
*
* symfony/translation is required to translate into a non-English locale.
*
* @throws \LogicException
*/
public function getExpirationMessageKey(): string
{
$interval = $this->getExpiresAtIntervalInstance();

switch ($interval) {
case $interval->y > 0:
$this->transInterval = $interval->y;

return '%count% year|%count% years';
case $interval->m > 0:
$this->transInterval = $interval->m;

return '%count% month|%count% months';
case $interval->d > 0:
$this->transInterval = $interval->d;

return '%count% day|%count% days';
case $interval->h > 0:
$this->transInterval = $interval->h;

return '%count% hour|%count% hours';
default:
$this->transInterval = $interval->i;

return '%count% minute|%count% minutes';
}
}

/**
* @throws \LogicException
*/
public function getExpirationMessageData(): array
{
$this->getExpirationMessageKey();

return ['%count%' => $this->transInterval];
}

/**
* Get the interval that the signature is valid for.
*
* @throws \LogicException
*
* @psalm-suppress PossiblyFalseArgument
*/
public function getExpiresAtIntervalInstance(): \DateInterval
{
if (null === $this->generatedAt) {
throw new \LogicException(sprintf('%s initialized without setting the $generatedAt timestamp.', self::class));
}

$createdAtTime = \DateTimeImmutable::createFromFormat('U', (string) $this->generatedAt);

return $this->expiresAt->diff($createdAtTime);
}

private function triggerDeprecation(): void
{
trigger_deprecation(
'symfonycasts/verify-email-bundle',
'1.2',
'Initializing the %s without setting the "$generatedAt" constructor argument is deprecated. The default "null" will be removed in the future.',
self::class
);
}
}
27 changes: 27 additions & 0 deletions src/Resources/translations/VerifyEmailBundle.en.xlf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="file.ext" source-language="en" datatype="plaintext">
<body>
<trans-unit id="1">
<source>%count% year|%count% years</source>
<target>%count% year|%count% years</target>
</trans-unit>
<trans-unit id="2">
<source>%count% month|%count% months</source>
<target>%count% month|%count% months</target>
</trans-unit>
<trans-unit id="3">
<source>%count% day|%count% days</source>
<target>%count% day|%count% days</target>
</trans-unit>
<trans-unit id="4">
<source>%count% hour|%count% hours</source>
<target>%count% hour|%count% hours</target>
</trans-unit>
<trans-unit id="5">
<source>%count% minute|%count% minutes</source>
<target>%count% minute|%count% minutes</target>
</trans-unit>
</body>
</file>
</xliff>
5 changes: 3 additions & 2 deletions src/VerifyEmailHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public function __construct(UrlGeneratorInterface $router, UriSigner $uriSigner,
*/
public function generateSignature(string $routeName, string $userId, string $userEmail, array $extraParams = []): VerifyEmailSignatureComponents
{
$expiryTimestamp = time() + $this->lifetime;
$generatedAt = time();
$expiryTimestamp = $generatedAt + $this->lifetime;

$extraParams['token'] = $this->tokenGenerator->createToken($userId, $userEmail);
$extraParams['expires'] = $expiryTimestamp;
Expand All @@ -58,7 +59,7 @@ public function generateSignature(string $routeName, string $userId, string $use
$signature = $this->uriSigner->sign($uri);

/** @psalm-suppress PossiblyFalseArgument */
return new VerifyEmailSignatureComponents(\DateTimeImmutable::createFromFormat('U', (string) $expiryTimestamp), $signature);
return new VerifyEmailSignatureComponents(\DateTimeImmutable::createFromFormat('U', (string) $expiryTimestamp), $signature, $generatedAt);
}

/**
Expand Down
63 changes: 63 additions & 0 deletions tests/UnitTests/Model/VerifyEmailSignatureComponentsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

/*
* This file is part of the SymfonyCasts VerifyEmailBundle package.
* Copyright (c) SymfonyCasts <https://symfonycasts.com/>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SymfonyCasts\Bundle\VerifyEmail\Tests\UnitTests\Model;

use PHPUnit\Framework\TestCase;
use SymfonyCasts\Bundle\VerifyEmail\Model\VerifyEmailSignatureComponents;

/**
* @author Jesse Rushlow <jr@rushlow.dev>
*/
class VerifyEmailSignatureComponentsTest extends TestCase
{
public function testGetExpiresAtInterval(): void
{
$created = time();

$expire = \DateTimeImmutable::createFromFormat('U', (string) ($created + 3600));

$components = new VerifyEmailSignatureComponents($expire, 'some-uri', $created);

self::assertSame(1, $components->getExpiresAtIntervalInstance()->h);
}

/**
* @dataProvider translationIntervalDataProvider
*/
public function testTranslations(int $lifetime, int $expectedInterval, string $unitOfMeasure): void
{
$created = time();

$expire = \DateTimeImmutable::createFromFormat('U', (string) ($created + $lifetime));

$components = new VerifyEmailSignatureComponents($expire, 'some-uri', $created);

self::assertSame(
sprintf('%%count%% %s|%%count%% %ss', $unitOfMeasure, $unitOfMeasure),
$components->getExpirationMessageKey()
);

self::assertSame(['%count%' => $expectedInterval], $components->getExpirationMessageData());
}

public function translationIntervalDataProvider(): \Generator
{
yield [60, 1, 'minute'];
yield [900, 15, 'minute'];
yield [3600, 1, 'hour'];
yield [7200, 2, 'hour'];
yield [43200, 12, 'hour'];
yield [86400, 1, 'day'];
yield [864000, 10, 'day'];
yield [2678400, 1, 'month'];
yield [5356800, 2, 'month'];
yield [34819200, 1, 'year'];
}
}

0 comments on commit fe19842

Please sign in to comment.