diff --git a/.github/workflows/infection.yml b/.github/workflows/infection.yml
new file mode 100644
index 0000000..526db27
--- /dev/null
+++ b/.github/workflows/infection.yml
@@ -0,0 +1,34 @@
+# yaml-language-server: $schema=https://json.schemastore.org/github-workflow
+
+name: "Infection"
+
+on:
+ push:
+ branches:
+ - "*.x"
+
+jobs:
+ mutation_testing:
+ name: "5️⃣ Mutation Testing"
+ steps:
+ - name: "Set up PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ php-version: "8.1"
+ extensions: "mbstring"
+ coverage: "xdebug"
+
+ - name: "Checkout code"
+ uses: "actions/checkout@v3"
+
+ - name: "Fetch Git base reference"
+ run: "git fetch --depth=1 origin ${GITHUB_BASE_REF}"
+
+ - name: "Install dependencies"
+ uses: "ramsey/composer-install@v2"
+ with:
+ dependency-versions: "highest"
+ composer-options: "--optimize-autoloader"
+
+ - name: "Execute Infection"
+ run: "make ci-mu"
diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml
index 5578d06..4cd88ab 100644
--- a/.github/workflows/integrate.yml
+++ b/.github/workflows/integrate.yml
@@ -56,6 +56,8 @@ jobs:
- "ubuntu-latest"
php-version:
- "8.1"
+ - "8.2"
+ - "8.3"
dependencies:
- "lowest"
- "highest"
@@ -80,14 +82,6 @@ jobs:
- name: "Execute tests (PHP)"
run: "make ci-cc"
- # - name: Send coverage to Coveralls
- # if: "matrix.php-version == '8.1' && matrix.dependencies == 'highest'"
- # env:
- # COVERALLS_REPO_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- # run: |
- # wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.2/php-coveralls.phar"
- # php ./php-coveralls.phar -v
-
static_analysis:
name: "3️⃣ Static Analysis"
needs:
@@ -153,35 +147,6 @@ jobs:
run: |
vendor/bin/deptrac analyse --fail-on-uncovered --no-cache
- mutation_testing:
- name: "5️⃣ Mutation Testing"
- needs:
- - "byte_level"
- - "syntax_errors"
- runs-on: "ubuntu-latest"
- steps:
- - name: "Set up PHP"
- uses: "shivammathur/setup-php@v2"
- with:
- php-version: "8.1"
- extensions: "mbstring"
- coverage: "xdebug"
-
- - name: "Checkout code"
- uses: "actions/checkout@v3"
-
- - name: "Fetch Git base reference"
- run: "git fetch --depth=1 origin ${GITHUB_BASE_REF}"
-
- - name: "Install dependencies"
- uses: "ramsey/composer-install@v2"
- with:
- dependency-versions: "highest"
- composer-options: "--optimize-autoloader"
-
- - name: "Execute Infection"
- run: "make ci-mu"
-
rector_checkstyle:
name: "6️⃣ Rector Checkstyle"
needs:
diff --git a/README.md b/README.md
index b23282e..542de6f 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@ TOTP / HOTP library in PHP
==========================
![Build Status](https://github.com/spomky-labs/otphp/workflows/Integrate/badge.svg)
+![Build Status](https://github.com/spomky-labs/otphp/workflows/Infection/badge.svg)
[![Latest Stable Version](https://poser.pugx.org/spomky-labs/otphp/v/stable.png)](https://packagist.org/packages/spomky-labs/otphp)
[![Total Downloads](https://poser.pugx.org/spomky-labs/otphp/downloads.png)](https://packagist.org/packages/spomky-labs/otphp)
diff --git a/composer.json b/composer.json
index 080df37..33901f7 100644
--- a/composer.json
+++ b/composer.json
@@ -22,17 +22,17 @@
},
"require-dev": {
"ekino/phpstan-banned-code": "^1.0",
- "infection/infection": "^0.26",
+ "infection/infection": "^0.26|^0.27|^0.28",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
- "phpunit/phpunit": "^9.5.26",
+ "phpunit/phpunit": "^9.5.26|^10.0|^11.0",
"qossmic/deptrac-shim": "^1.0",
- "rector/rector": "^0.15",
- "symfony/phpunit-bridge": "^6.1",
- "symplify/easy-coding-standard": "^11.0"
+ "rector/rector": "1.0",
+ "symfony/phpunit-bridge": "^6.1|^7.0",
+ "symplify/easy-coding-standard": "^12.0"
},
"autoload": {
"psr-4": { "OTPHP\\": "src/" }
diff --git a/ecs.php b/ecs.php
index fc51758..8df6aa3 100644
--- a/ecs.php
+++ b/ecs.php
@@ -30,9 +30,7 @@
use Symplify\EasyCodingStandard\Config\ECSConfig;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList;
-$header = '';
-
-return static function (ECSConfig $config) use ($header): void {
+return static function (ECSConfig $config): void {
$config->import(SetList::PSR_12);
$config->import(SetList::CLEAN_CODE);
$config->import(SetList::DOCTRINE_ANNOTATIONS);
@@ -74,7 +72,7 @@
'strict' => true,
]);
$config->ruleWithConfiguration(HeaderCommentFixer::class, [
- 'header' => $header,
+ 'header' => '',
]);
$config->ruleWithConfiguration(AlignMultilineCommentFixer::class, [
'comment_type' => 'all_multiline',
@@ -88,13 +86,7 @@
'import_functions' => true,
]);
- $config->services()
- ->remove(PhpUnitTestClassRequiresCoversFixer::class)
- ;
-
$config->parallel();
- $config->paths([
- __DIR__.'/src',
- __DIR__.'/tests',
- ]);
+ $config->paths([__DIR__]);
+ $config->skip([__DIR__ . '/vendor', PhpUnitTestClassRequiresCoversFixer::class]);
};
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index e52c02c..61963d4 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -3,26 +3,16 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="vendor/autoload.php"
colors="true"
- resolveDependencies="true"
- xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
+ xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
>
-
-
- ./src
-
-
./tests
-
-
-
-
- OTPHP\TOTP
-
-
-
-
+
diff --git a/rector.php b/rector.php
index d781ad4..d82cf27 100644
--- a/rector.php
+++ b/rector.php
@@ -3,36 +3,24 @@
declare(strict_types=1);
use Rector\Config\RectorConfig;
-use Rector\Core\ValueObject\PhpVersion;
-use Rector\Doctrine\Set\DoctrineSetList;
-use Rector\Php74\Rector\Property\TypedPropertyRector;
-use Rector\PHPUnit\Set\PHPUnitLevelSetList;
+use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector;
use Rector\PHPUnit\Set\PHPUnitSetList;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
-use Rector\Symfony\Set\SymfonyLevelSetList;
use Rector\Symfony\Set\SymfonySetList;
+use Rector\ValueObject\PhpVersion;
return static function (RectorConfig $config): void {
- $config->sets([
- SetList::DEAD_CODE,
- LevelSetList::UP_TO_PHP_81,
- SymfonyLevelSetList::UP_TO_SYMFONY_54,
- SymfonySetList::SYMFONY_CODE_QUALITY,
- SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
- DoctrineSetList::DOCTRINE_CODE_QUALITY,
- DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
- PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD,
- PHPUnitLevelSetList::UP_TO_PHPUNIT_100,
- PHPUnitSetList::PHPUNIT_CODE_QUALITY,
- PHPUnitSetList::PHPUNIT_EXCEPTION,
- PHPUnitSetList::REMOVE_MOCKS,
- PHPUnitSetList::PHPUNIT_YIELD_DATA_PROVIDER,
- ]);
+ $config->import(SetList::DEAD_CODE);
+ $config->import(LevelSetList::UP_TO_PHP_80);
+ $config->import(SymfonySetList::SYMFONY_CODE_QUALITY);
+ $config->import(PHPUnitSetList::PHPUNIT_100);
+ $config->import(PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES);
+ $config->import(PHPUnitSetList::PHPUNIT_CODE_QUALITY);
$config->parallel();
- $config->paths([__DIR__ . '/src']);
+ $config->paths([__DIR__ . '/src', __DIR__ . '/tests']);
+ $config->skip([PreferPHPUnitThisCallRector::class]);
$config->phpVersion(PhpVersion::PHP_81);
$config->importNames();
$config->importShortClasses();
-
};
diff --git a/src/Factory.php b/src/Factory.php
index 409d875..f58e838 100644
--- a/src/Factory.php
+++ b/src/Factory.php
@@ -4,10 +4,10 @@
namespace OTPHP;
-use function assert;
-use function count;
use InvalidArgumentException;
use Throwable;
+use function assert;
+use function count;
/**
* This class is used to load OTP object from a provisioning Uri.
diff --git a/src/OTP.php b/src/OTP.php
index 7e68dfc..e042917 100644
--- a/src/OTP.php
+++ b/src/OTP.php
@@ -4,14 +4,14 @@
namespace OTPHP;
-use function assert;
-use function chr;
-use function count;
use Exception;
use InvalidArgumentException;
-use function is_string;
use ParagonIE\ConstantTime\Base32;
use RuntimeException;
+use function assert;
+use function chr;
+use function count;
+use function is_string;
use const STR_PAD_LEFT;
abstract class OTP implements OTPInterface
diff --git a/src/ParameterTrait.php b/src/ParameterTrait.php
index 3b2641e..dc92861 100644
--- a/src/ParameterTrait.php
+++ b/src/ParameterTrait.php
@@ -4,10 +4,10 @@
namespace OTPHP;
+use InvalidArgumentException;
use function array_key_exists;
use function assert;
use function in_array;
-use InvalidArgumentException;
use function is_int;
use function is_string;
diff --git a/src/TOTP.php b/src/TOTP.php
index 3a7d728..8a1cfeb 100644
--- a/src/TOTP.php
+++ b/src/TOTP.php
@@ -4,8 +4,8 @@
namespace OTPHP;
-use function assert;
use InvalidArgumentException;
+use function assert;
use function is_int;
/**
@@ -133,23 +133,21 @@ public function setEpoch(int $epoch): void
*/
protected function getParameterMap(): array
{
- return array_merge(
- parent::getParameterMap(),
- [
- 'period' => static function ($value): int {
- (int) $value > 0 || throw new InvalidArgumentException('Period must be at least 1.');
-
- return (int) $value;
- },
- 'epoch' => static function ($value): int {
- (int) $value >= 0 || throw new InvalidArgumentException(
- 'Epoch must be greater than or equal to 0.'
- );
-
- return (int) $value;
- },
- ]
- );
+ return [
+ ...parent::getParameterMap(),
+ 'period' => static function ($value): int {
+ (int) $value > 0 || throw new InvalidArgumentException('Period must be at least 1.');
+
+ return (int) $value;
+ },
+ 'epoch' => static function ($value): int {
+ (int) $value >= 0 || throw new InvalidArgumentException(
+ 'Epoch must be greater than or equal to 0.'
+ );
+
+ return (int) $value;
+ },
+ ];
}
/**
diff --git a/src/Url.php b/src/Url.php
index 76919d2..a97ca68 100644
--- a/src/Url.php
+++ b/src/Url.php
@@ -4,8 +4,8 @@
namespace OTPHP;
-use function array_key_exists;
use InvalidArgumentException;
+use function array_key_exists;
use function is_string;
/**
diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php
index dea4e03..e069bc3 100644
--- a/tests/FactoryTest.php
+++ b/tests/FactoryTest.php
@@ -8,6 +8,7 @@
use OTPHP\Factory;
use OTPHP\HOTP;
use OTPHP\TOTP;
+use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
/**
@@ -15,9 +16,7 @@
*/
final class FactoryTest extends TestCase
{
- /**
- * @test
- */
+ #[Test]
public function tOTPLoad(): void
{
$otp = 'otpauth://totp/My%20Project%3Aalice%40foo.bar?algorithm=sha512&digits=8&foo=bar.baz&issuer=My%20Project&period=20&secret=JDDK4U6G3BJLEZ7Y';
@@ -36,9 +35,7 @@ public function tOTPLoad(): void
static::assertSame($otp, $result->getProvisioningUri());
}
- /**
- * @test
- */
+ #[Test]
public function tOTPObjectDoesNotHaveRequestedParameter(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -49,9 +46,7 @@ public function tOTPObjectDoesNotHaveRequestedParameter(): void
$result->getParameter('image');
}
- /**
- * @test
- */
+ #[Test]
public function hOTPLoad(): void
{
$otp = 'otpauth://hotp/My%20Project%3Aalice%40foo.bar?counter=1000&digits=8&image=https%3A%2F%2Ffoo.bar%2Fbaz&issuer=My%20Project&secret=JDDK4U6G3BJLEZ7Y';
@@ -69,9 +64,7 @@ public function hOTPLoad(): void
static::assertSame($otp, $result->getProvisioningUri());
}
- /**
- * @test
- */
+ #[Test]
public function badProvisioningUri1(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -80,9 +73,7 @@ public function badProvisioningUri1(): void
Factory::loadFromProvisioningUri($otp);
}
- /**
- * @test
- */
+ #[Test]
public function badProvisioningUri2(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -91,9 +82,7 @@ public function badProvisioningUri2(): void
Factory::loadFromProvisioningUri($otp);
}
- /**
- * @test
- */
+ #[Test]
public function badProvisioningUri3(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -102,9 +91,7 @@ public function badProvisioningUri3(): void
Factory::loadFromProvisioningUri($otp);
}
- /**
- * @test
- */
+ #[Test]
public function badProvisioningUri4(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -113,9 +100,7 @@ public function badProvisioningUri4(): void
Factory::loadFromProvisioningUri($otp);
}
- /**
- * @test
- */
+ #[Test]
public function badProvisioningUri5(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -124,9 +109,7 @@ public function badProvisioningUri5(): void
Factory::loadFromProvisioningUri($otp);
}
- /**
- * @test
- */
+ #[Test]
public function badProvisioningUri6(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -135,9 +118,7 @@ public function badProvisioningUri6(): void
Factory::loadFromProvisioningUri($otp);
}
- /**
- * @test
- */
+ #[Test]
public function tOTPLoadWithoutIssuer(): void
{
$otp = 'otpauth://totp/My%20Test%20-%20Auth?secret=JDDK4U6G3BJLEZ7Y';
@@ -154,9 +135,7 @@ public function tOTPLoadWithoutIssuer(): void
static::assertSame($otp, $result->getProvisioningUri());
}
- /**
- * @test
- */
+ #[Test]
public function tOTPLoadAndRemoveSecretTrailingCharacters(): void
{
$uri = 'otpauth://totp/My%20Test%20-%20Auth?secret=JDDK4U6G3BJLEQ%3D%3D';
diff --git a/tests/HOTPTest.php b/tests/HOTPTest.php
index 06ab342..6c65caf 100644
--- a/tests/HOTPTest.php
+++ b/tests/HOTPTest.php
@@ -6,6 +6,7 @@
use InvalidArgumentException;
use OTPHP\HOTP;
+use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
@@ -14,9 +15,7 @@
*/
final class HOTPTest extends TestCase
{
- /**
- * @test
- */
+ #[Test]
public function labelNotDefined(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -25,9 +24,7 @@ public function labelNotDefined(): void
$hotp->getProvisioningUri();
}
- /**
- * @test
- */
+ #[Test]
public function issuerHasColon(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -37,9 +34,7 @@ public function issuerHasColon(): void
$otp->setIssuer('foo%3Abar');
}
- /**
- * @test
- */
+ #[Test]
public function issuerHasColon2(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -49,9 +44,7 @@ public function issuerHasColon2(): void
$otp->setIssuer('foo%3abar');
}
- /**
- * @test
- */
+ #[Test]
public function labelHasColon(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -61,9 +54,7 @@ public function labelHasColon(): void
$otp->setLabel('foo%3Abar');
}
- /**
- * @test
- */
+ #[Test]
public function labelHasColon2(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -73,9 +64,7 @@ public function labelHasColon2(): void
$otp->setLabel('foo:bar');
}
- /**
- * @test
- */
+ #[Test]
public function digitsIsNot1OrMore(): void
{
$htop = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -85,9 +74,7 @@ public function digitsIsNot1OrMore(): void
$htop->setDigits(0);
}
- /**
- * @test
- */
+ #[Test]
public function counterIsNot1OrMore(): void
{
$htop = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -97,9 +84,7 @@ public function counterIsNot1OrMore(): void
$htop->setCounter(-500);
}
- /**
- * @test
- */
+ #[Test]
public function digestIsNotSupported(): void
{
$htop = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -111,9 +96,8 @@ public function digestIsNotSupported(): void
/**
* xpectedExceptionMessage.
- *
- * @test
*/
+ #[Test]
public function secretShouldBeBase32Encoded(): void
{
$otp = HOTP::createFromSecret(random_bytes(32));
@@ -123,9 +107,7 @@ public function secretShouldBeBase32Encoded(): void
$otp->at(0);
}
- /**
- * @test
- */
+ #[Test]
public function objectCreationValid(): void
{
$otp = HOTP::generate();
@@ -133,9 +115,7 @@ public function objectCreationValid(): void
static::assertMatchesRegularExpression('/^[A-Z2-7]+$/', $otp->getSecret());
}
- /**
- * @test
- */
+ #[Test]
public function getProvisioningUri(): void
{
$otp = $this->createHOTP(8, 'sha1', 1000);
@@ -147,9 +127,7 @@ public function getProvisioningUri(): void
);
}
- /**
- * @test
- */
+ #[Test]
public function verifyCounterInvalid(): void
{
$otp = $this->createHOTP(8, 'sha1', 1000);
@@ -157,9 +135,7 @@ public function verifyCounterInvalid(): void
static::assertFalse($otp->verify('98449994', 100));
}
- /**
- * @test
- */
+ #[Test]
public function verifyCounterChanged(): void
{
$otp = $this->createHOTP(8, 'sha1', 1100);
@@ -169,9 +145,7 @@ public function verifyCounterChanged(): void
static::assertSame($otp->getCounter(), 1101);
}
- /**
- * @test
- */
+ #[Test]
public function verifyValidInWindow(): void
{
$otp = $this->createHOTP(8, 'sha1', 1000);
diff --git a/tests/TOTPTest.php b/tests/TOTPTest.php
index c868a75..3deeae9 100644
--- a/tests/TOTPTest.php
+++ b/tests/TOTPTest.php
@@ -4,23 +4,24 @@
namespace OTPHP\Test;
-use function assert;
use InvalidArgumentException;
+use Iterator;
use OTPHP\TOTP;
use OTPHP\TOTPInterface;
use ParagonIE\ConstantTime\Base32;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Bridge\PhpUnit\ClockMock;
+use function assert;
/**
* @internal
*/
final class TOTPTest extends TestCase
{
- /**
- * @test
- */
+ #[Test]
public function labelNotDefined(): void
{
$this->expectException(InvalidArgumentException::class);
@@ -29,9 +30,7 @@ public function labelNotDefined(): void
$otp->getProvisioningUri();
}
- /**
- * @test
- */
+ #[Test]
public function customParameter(): void
{
$otp = TOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -49,9 +48,7 @@ public function customParameter(): void
);
}
- /**
- * @test
- */
+ #[Test]
public function objectCreationValid(): void
{
$otp = TOTP::generate();
@@ -59,9 +56,7 @@ public function objectCreationValid(): void
static::assertMatchesRegularExpression('/^[A-Z2-7]+$/', $otp->getSecret());
}
- /**
- * @test
- */
+ #[Test]
public function periodIsNot1OrMore(): void
{
$totp = TOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -71,9 +66,7 @@ public function periodIsNot1OrMore(): void
$totp->setPeriod(-20);
}
- /**
- * @test
- */
+ #[Test]
public function epochIsNot0OrMore(): void
{
$totp = TOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
@@ -83,9 +76,7 @@ public function epochIsNot0OrMore(): void
$totp->setEpoch(-1);
}
- /**
- * @test
- */
+ #[Test]
public function secretShouldBeBase32Encoded(): void
{
$this->expectException(RuntimeException::class);
@@ -96,12 +87,10 @@ public function secretShouldBeBase32Encoded(): void
$otp->now();
}
- /**
- * @test
- */
+ #[Test]
public function getProvisioningUri(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
static::assertSame(
'otpauth://totp/My%20Project%3Aalice%40foo.bar?issuer=My%20Project&secret=JDDK4U6G3BJLEZ7Y',
@@ -109,9 +98,7 @@ public function getProvisioningUri(): void
);
}
- /**
- * @test
- */
+ #[Test]
public function getProvisioningUriWithNonDefaultArgSeperator(): void
{
$otp = self::createTOTP(6, 'sha1', 30);
@@ -128,48 +115,42 @@ public function getProvisioningUriWithNonDefaultArgSeperator(): void
* @param positive-int $timestamp
* @param positive-int $period
* @param positive-int $expectedRemainder
- * @test
- * @dataProvider dataRemainingTimeBeforeExpiration
*/
+ #[DataProvider('dataRemainingTimeBeforeExpiration')]
+ #[Test]
public function getRemainingTimeBeforeExpiration(int $timestamp, int $period, int $expectedRemainder): void
{
ClockMock::register(TOTP::class);
ClockMock::withClockMock($timestamp);
- $otp = $this->createTOTP(6, 'sha1', $period);
+ $otp = self::createTOTP(6, 'sha1', $period);
static::assertSame($expectedRemainder, $otp->expiresIn());
}
- /**
- * @test
- */
+ #[Test]
public function generateOtpAt(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
static::assertSame('855783', $otp->at(0));
static::assertSame('762124', $otp->at(319690800));
static::assertSame('139664', $otp->at(1301012137));
}
- /**
- * @test
- */
+ #[Test]
public function generateOtpWithEpochAt(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30, 'JDDK4U6G3BJLEZ7Y', 'alice@foo.bar', 'My Project', 100);
+ $otp = self::createTOTP(6, 'sha1', 30, 'JDDK4U6G3BJLEZ7Y', 'alice@foo.bar', 'My Project', 100);
static::assertSame('855783', $otp->at(100));
static::assertSame('762124', $otp->at(319690900));
static::assertSame('139664', $otp->at(1301012237));
}
- /**
- * @test
- */
+ #[Test]
public function wrongSizeOtp(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
static::assertFalse($otp->verify('0'));
static::assertFalse($otp->verify('00'));
@@ -178,39 +159,33 @@ public function wrongSizeOtp(): void
static::assertFalse($otp->verify('00000'));
}
- /**
- * @test
- */
+ #[Test]
public function generateOtpNow(): void
{
ClockMock::register(TOTP::class);
$time = time();
ClockMock::withClockMock($time);
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
static::assertSame($otp->now(), $otp->at($time));
}
- /**
- * @test
- */
+ #[Test]
public function verifyOtpNow(): void
{
ClockMock::register(TOTP::class);
$time = time();
ClockMock::withClockMock($time);
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
$totp = $otp->at($time);
static::assertTrue($otp->verify($totp, $time));
}
- /**
- * @test
- */
+ #[Test]
public function verifyOtp(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
static::assertTrue($otp->verify('855783', 0));
static::assertTrue($otp->verify('762124', 319690800));
@@ -221,12 +196,10 @@ public function verifyOtp(): void
static::assertFalse($otp->verify('139664', 1301012197));
}
- /**
- * @test
- */
+ #[Test]
public function verifyOtpWithEpoch(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30, 'JDDK4U6G3BJLEZ7Y', 'alice@foo.bar', 'My Project', 100);
+ $otp = self::createTOTP(6, 'sha1', 30, 'JDDK4U6G3BJLEZ7Y', 'alice@foo.bar', 'My Project', 100);
static::assertTrue($otp->verify('855783', 100));
static::assertTrue($otp->verify('762124', 319690900));
@@ -237,12 +210,10 @@ public function verifyOtpWithEpoch(): void
static::assertFalse($otp->verify('139664', 1301012297));
}
- /**
- * @test
- */
+ #[Test]
public function notCompatibleWithGoogleAuthenticator(): void
{
- $otp = $this->createTOTP(9, 'sha512', 10);
+ $otp = self::createTOTP(9, 'sha512', 10);
static::assertSame(
'otpauth://totp/My%20Project%3Aalice%40foo.bar?algorithm=sha512&digits=9&issuer=My%20Project&period=10&secret=JDDK4U6G3BJLEZ7Y',
@@ -251,14 +222,12 @@ public function notCompatibleWithGoogleAuthenticator(): void
}
/**
- * @dataProvider dataVectors
- *
* @param TOTPInterface $totp
* @param positive-int $timestamp
* @param non-empty-string $expected_value
- *
- * @test
*/
+ #[DataProvider('dataVectors')]
+ #[Test]
public function vectors($totp, $timestamp, $expected_value): void
{
static::assertSame($expected_value, $totp->at($timestamp));
@@ -268,51 +237,44 @@ public function vectors($totp, $timestamp, $expected_value): void
/**
* @see https://tools.ietf.org/html/rfc6238#appendix-B
* @see http://www.rfc-editor.org/errata_search.php?rfc=6238
- *
- * @return array
*/
- public function dataVectors(): array
+ public static function dataVectors(): Iterator
{
$sha1key = Base32::encodeUpper('12345678901234567890');
assert($sha1key !== '');
- $totp_sha1 = $this->createTOTP(8, 'sha1', 30, $sha1key);
+ $totp_sha1 = self::createTOTP(8, 'sha1', 30, $sha1key);
$sha256key = Base32::encodeUpper('12345678901234567890123456789012');
assert($sha256key !== '');
- $totp_sha256 = $this->createTOTP(8, 'sha256', 30, $sha256key);
+ $totp_sha256 = self::createTOTP(8, 'sha256', 30, $sha256key);
$sha512key = Base32::encodeUpper('1234567890123456789012345678901234567890123456789012345678901234');
assert($sha512key !== '');
- $totp_sha512 = $this->createTOTP(8, 'sha512', 30, $sha512key);
-
- return [
- [$totp_sha1, 59, '94287082'],
- [$totp_sha256, 59, '46119246'],
- [$totp_sha512, 59, '90693936'],
- [$totp_sha1, 1111111109, '07081804'],
- [$totp_sha256, 1111111109, '68084774'],
- [$totp_sha512, 1111111109, '25091201'],
- [$totp_sha1, 1111111111, '14050471'],
- [$totp_sha256, 1111111111, '67062674'],
- [$totp_sha512, 1111111111, '99943326'],
- [$totp_sha1, 1234567890, '89005924'],
- [$totp_sha256, 1234567890, '91819424'],
- [$totp_sha512, 1234567890, '93441116'],
- [$totp_sha1, 2000000000, '69279037'],
- [$totp_sha256, 2000000000, '90698825'],
- [$totp_sha512, 2000000000, '38618901'],
- [$totp_sha1, 20000000000, '65353130'],
- [$totp_sha256, 20000000000, '77737706'],
- [$totp_sha512, 20000000000, '47863826'],
- ];
+ $totp_sha512 = self::createTOTP(8, 'sha512', 30, $sha512key);
+ yield [$totp_sha1, 59, '94287082'];
+ yield [$totp_sha256, 59, '46119246'];
+ yield [$totp_sha512, 59, '90693936'];
+ yield [$totp_sha1, 1111111109, '07081804'];
+ yield [$totp_sha256, 1111111109, '68084774'];
+ yield [$totp_sha512, 1111111109, '25091201'];
+ yield [$totp_sha1, 1111111111, '14050471'];
+ yield [$totp_sha256, 1111111111, '67062674'];
+ yield [$totp_sha512, 1111111111, '99943326'];
+ yield [$totp_sha1, 1234567890, '89005924'];
+ yield [$totp_sha256, 1234567890, '91819424'];
+ yield [$totp_sha512, 1234567890, '93441116'];
+ yield [$totp_sha1, 2000000000, '69279037'];
+ yield [$totp_sha256, 2000000000, '90698825'];
+ yield [$totp_sha512, 2000000000, '38618901'];
+ yield [$totp_sha1, 20000000000, '65353130'];
+ yield [$totp_sha256, 20000000000, '77737706'];
+ yield [$totp_sha512, 20000000000, '47863826'];
}
- /**
- * @test
- */
+ #[Test]
public function invalidOtpWindow(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The leeway must be lower than the TOTP period');
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
$otp->verify('123456', null, 31);
}
@@ -320,14 +282,14 @@ public function invalidOtpWindow(): void
* @param positive-int $timestamp
* @param non-empty-string $input
* @param 0|positive-int $leeway
- * @test
- * @dataProvider dataLeeway
*/
+ #[DataProvider('dataLeeway')]
+ #[Test]
public function verifyOtpInWindow(int $timestamp, string $input, int $leeway, bool $expectedResult): void
{
ClockMock::register(TOTP::class);
ClockMock::withClockMock($timestamp);
- $otp = $this->createTOTP(6, 'sha1', 30);
+ $otp = self::createTOTP(6, 'sha1', 30);
static::assertSame($expectedResult, $otp->verify($input, null, $leeway));
}
@@ -336,9 +298,9 @@ public function verifyOtpInWindow(int $timestamp, string $input, int $leeway, bo
* @param positive-int $timestamp
* @param non-empty-string $input
* @param 0|positive-int $leeway
- * @test
- * @dataProvider dataLeewayWithEpoch
*/
+ #[DataProvider('dataLeewayWithEpoch')]
+ #[Test]
public function verifyOtpWithEpochInWindow(
int $timestamp,
string $input,
@@ -347,37 +309,40 @@ public function verifyOtpWithEpochInWindow(
): void {
ClockMock::register(TOTP::class);
ClockMock::withClockMock($timestamp);
- $otp = $this->createTOTP(6, 'sha1', 30, 'JDDK4U6G3BJLEZ7Y', 'alice@foo.bar', 'My Project', 100);
+ $otp = self::createTOTP(6, 'sha1', 30, 'JDDK4U6G3BJLEZ7Y', 'alice@foo.bar', 'My Project', 100);
static::assertSame($expectedResult, $otp->verify($input, null, $leeway));
}
- /**
- * @return array[]
- */
- public function dataLeewayWithEpoch(): array
+ public static function dataLeewayWithEpoch(): Iterator
{
- return [
- [319690889, '762124', 10, false], //Leeway of 10 seconds, **out** the period of 11sec
- [319690890, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 10sec
- [319690899, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 1sec
- [319690899, '762124', 0, false], //No leeway, **out** the period
- [319690900, '762124', 0, true], //No leeway, in the period
- [319690920, '762124', 0, true], //No leeway, in the period
- [319690929, '762124', 0, true], //No leeway, in the period
- [319690930, '762124', 0, false], //No leeway, **out** the period
- [319690930, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 1sec
- [319690939, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 10sec
- [319690940, '762124', 10, false], //Leeway of 10 seconds, **out** the period of 11sec
- ];
+ yield [319690889, '762124', 10, false];
+ //Leeway of 10 seconds, **out** the period of 11sec
+ yield [319690890, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 10sec
+ yield [319690899, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 1sec
+ yield [319690899, '762124', 0, false];
+ //No leeway, **out** the period
+ yield [319690900, '762124', 0, true];
+ //No leeway, in the period
+ yield [319690920, '762124', 0, true];
+ //No leeway, in the period
+ yield [319690929, '762124', 0, true];
+ //No leeway, in the period
+ yield [319690930, '762124', 0, false];
+ //No leeway, **out** the period
+ yield [319690930, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 1sec
+ yield [319690939, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 10sec
+ yield [319690940, '762124', 10, false];
}
- /**
- * @test
- */
+ #[Test]
public function qRCodeUri(): void
{
- $otp = $this->createTOTP(6, 'sha1', 30, 'DJBSWY3DPEHPK3PXP', 'alice@google.com', 'My Big Compagny');
+ $otp = self::createTOTP(6, 'sha1', 30, 'DJBSWY3DPEHPK3PXP', 'alice@google.com', 'My Big Compagny');
static::assertSame(
'http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2FMy%2520Big%2520Compagny%253Aalice%2540google.com%3Fissuer%3DMy%2520Big%2520Compagny%26secret%3DDJBSWY3DPEHPK3PXP',
@@ -395,45 +360,45 @@ public function qRCodeUri(): void
);
}
- /**
- * @return int[][]
- */
- public function dataRemainingTimeBeforeExpiration(): array
+ public static function dataRemainingTimeBeforeExpiration(): Iterator
{
- return [
- [1644926810, 90, 40],
- [1644926810, 30, 10],
- [1644926810, 20, 10],
- [1577833199, 90, 1],
- [1577833199, 30, 1],
- [1577833199, 20, 1],
- [1577833200, 90, 90],
- [1577833200, 30, 30],
- [1577833200, 20, 20],
- [1577833201, 90, 89],
- [1577833201, 30, 29],
- [1577833201, 20, 19],
- ];
+ yield [1644926810, 90, 40];
+ yield [1644926810, 30, 10];
+ yield [1644926810, 20, 10];
+ yield [1577833199, 90, 1];
+ yield [1577833199, 30, 1];
+ yield [1577833199, 20, 1];
+ yield [1577833200, 90, 90];
+ yield [1577833200, 30, 30];
+ yield [1577833200, 20, 20];
+ yield [1577833201, 90, 89];
+ yield [1577833201, 30, 29];
+ yield [1577833201, 20, 19];
}
- /**
- * @return array[]
- */
- public function dataLeeway(): array
+ public static function dataLeeway(): Iterator
{
- return [
- [319690789, '762124', 10, false], //Leeway of 10 seconds, **out** the period of 11sec
- [319690790, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 10sec
- [319690799, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 1sec
- [319690799, '762124', 0, false], //No leeway, **out** the period
- [319690800, '762124', 0, true], //No leeway, in the period
- [319690820, '762124', 0, true], //No leeway, in the period
- [319690829, '762124', 0, true], //No leeway, in the period
- [319690830, '762124', 0, false], //No leeway, **out** the period
- [319690830, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 1sec
- [319690839, '762124', 10, true], //Leeway of 10 seconds, **out** the period of 10sec
- [319690840, '762124', 10, false], //Leeway of 10 seconds, **out** the period of 11sec
- ];
+ yield [319690789, '762124', 10, false];
+ //Leeway of 10 seconds, **out** the period of 11sec
+ yield [319690790, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 10sec
+ yield [319690799, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 1sec
+ yield [319690799, '762124', 0, false];
+ //No leeway, **out** the period
+ yield [319690800, '762124', 0, true];
+ //No leeway, in the period
+ yield [319690820, '762124', 0, true];
+ //No leeway, in the period
+ yield [319690829, '762124', 0, true];
+ //No leeway, in the period
+ yield [319690830, '762124', 0, false];
+ //No leeway, **out** the period
+ yield [319690830, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 1sec
+ yield [319690839, '762124', 10, true];
+ //Leeway of 10 seconds, **out** the period of 10sec
+ yield [319690840, '762124', 10, false];
}
/**
@@ -445,7 +410,7 @@ public function dataLeeway(): array
* @param non-empty-string $issuer
* @param 0|positive-int $epoch
*/
- private function createTOTP(
+ private static function createTOTP(
int $digits,
string $digest,
int $period,