diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 9dac0e0a47e112c944840b7aaff2226a1eb02620..584b524ce46cc9e56a98ab466981028fd1b56fa4 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['8.0', '8.1'] + php-versions: ['8.1'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/mutation-tests.yml b/.github/workflows/mutation-tests.yml index 936e4a6b3662cb5177fa31393ed941b1bbb9307e..2277a9821695e58b1b6aee7a8c6316725d335bdd 100644 --- a/.github/workflows/mutation-tests.yml +++ b/.github/workflows/mutation-tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['8.0', '8.1'] + php-versions: ['8.1'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/rector_checkstyle.yaml b/.github/workflows/rector_checkstyle.yaml index 7893810e6e78c7cbd3e6b88b4e19e05c4d7ac290..53f8d36d08fb46b719bead12c8902ca0fd68b9d8 100644 --- a/.github/workflows/rector_checkstyle.yaml +++ b/.github/workflows/rector_checkstyle.yaml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - php-versions: ['8.0', '8.1'] + php-versions: ['8.1'] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/static-analyze.yml b/.github/workflows/static-analyze.yml index e83ee2d8348f1bbcb753e83cdbf43b341fe5ecaa..91154b0e13913726c515274becbcb619c61a1bf9 100644 --- a/.github/workflows/static-analyze.yml +++ b/.github/workflows/static-analyze.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['8.0', '8.1'] + php-versions: ['8.1'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd4c13afa4a2f5e3d4ee2e3acadcd4f1255521fe..71f18a43e684c2c355968d7db0fae5a3e93a3a72 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - php-versions: ['8.0', '8.1'] + php-versions: ['8.1'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: diff --git a/composer.json b/composer.json index 4249162c1c211421f1685d83674d460993a13bd7..7102414e3304fc99324c9528d52cdefdbdb0da91 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "ext-mbstring": "*", "paragonie/constant_time_encoding": "^2.0", "beberlei/assert": "^3.0", @@ -31,9 +31,9 @@ "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.12.11", + "rector/rector": "^0.13", "symfony/phpunit-bridge": "^6.0", - "symplify/easy-coding-standard": "^10.0", + "symplify/easy-coding-standard": "^11.0", "thecodingmachine/phpstan-safe-rule": "^1.0" }, "autoload": { diff --git a/doc/UPGRADE_v10-v11.md b/doc/UPGRADE_v10-v11.md index df08eda04206783bc413f9fc3fecb5f7758fd296..e31ecba9bf7aa8f019b8fc55f05684f37007f41b 100644 --- a/doc/UPGRADE_v10-v11.md +++ b/doc/UPGRADE_v10-v11.md @@ -1,3 +1,4 @@ # Upgrade from `v10.x` to `v11.x` -Congratulation, you have nothing to do! \ No newline at end of file +Congratulation, you have nothing to do! +This version requires PHP8.1+, but no changes on your side are expected. diff --git a/ecs.php b/ecs.php index cccdf766486789582628b8b03e954fd4cb90f476..fc517584c98cdc95c756265c9150fa4250be20e8 100644 --- a/ecs.php +++ b/ecs.php @@ -14,7 +14,6 @@ use PhpCsFixer\Fixer\Import\OrderedImportsFixer; use PhpCsFixer\Fixer\LanguageConstruct\CombineConsecutiveIssetsFixer; use PhpCsFixer\Fixer\LanguageConstruct\CombineConsecutiveUnsetsFixer; use PhpCsFixer\Fixer\Phpdoc\AlignMultilineCommentFixer; -use PhpCsFixer\Fixer\Phpdoc\GeneralPhpdocAnnotationRemoveFixer; use PhpCsFixer\Fixer\Phpdoc\NoSuperfluousPhpdocTagsFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocOrderFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocTrimConsecutiveBlankLineSeparationFixer; @@ -28,91 +27,74 @@ use PhpCsFixer\Fixer\Strict\StrictComparisonFixer; use PhpCsFixer\Fixer\Strict\StrictParamFixer; use PhpCsFixer\Fixer\Whitespace\ArrayIndentationFixer; use PhpCsFixer\Fixer\Whitespace\CompactNullableTypehintFixer; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -use Symplify\EasyCodingStandard\ValueObject\Option; +use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; -return static function (ContainerConfigurator $containerConfigurator): void { - $header = ''; +$header = ''; - $containerConfigurator->import(SetList::PSR_12); - $containerConfigurator->import(SetList::PHP_CS_FIXER); - $containerConfigurator->import(SetList::PHP_CS_FIXER_RISKY); - $containerConfigurator->import(SetList::CLEAN_CODE); - $containerConfigurator->import(SetList::SYMFONY); - $containerConfigurator->import(SetList::DOCTRINE_ANNOTATIONS); - $containerConfigurator->import(SetList::SPACES); - $containerConfigurator->import(SetList::PHPUNIT); - $containerConfigurator->import(SetList::SYMPLIFY); - $containerConfigurator->import(SetList::ARRAY); - $containerConfigurator->import(SetList::COMMON); - $containerConfigurator->import(SetList::COMMENTS); - $containerConfigurator->import(SetList::CONTROL_STRUCTURES); - $containerConfigurator->import(SetList::DOCBLOCK); - $containerConfigurator->import(SetList::NAMESPACES); - $containerConfigurator->import(SetList::STRICT); +return static function (ECSConfig $config) use ($header): void { + $config->import(SetList::PSR_12); + $config->import(SetList::CLEAN_CODE); + $config->import(SetList::DOCTRINE_ANNOTATIONS); + $config->import(SetList::SPACES); + $config->import(SetList::PHPUNIT); + $config->import(SetList::SYMPLIFY); + $config->import(SetList::ARRAY); + $config->import(SetList::COMMON); + $config->import(SetList::COMMENTS); + $config->import(SetList::CONTROL_STRUCTURES); + $config->import(SetList::DOCBLOCK); + $config->import(SetList::NAMESPACES); + $config->import(SetList::STRICT); - $services = $containerConfigurator->services(); - $services->set(StrictParamFixer::class); - $services->set(StrictComparisonFixer::class); - $services->set(ArraySyntaxFixer::class) - ->call('configure', [[ - 'syntax' => 'short', - ]]) - ; - $services->set(ArrayIndentationFixer::class); - $services->set(OrderedImportsFixer::class); - $services->set(ProtectedToPrivateFixer::class); - $services->set(DeclareStrictTypesFixer::class); - $services->set(NativeConstantInvocationFixer::class); - $services->set(NativeFunctionInvocationFixer::class) - ->call('configure', [[ - 'include' => ['@compiler_optimized'], - 'scope' => 'namespaced', - 'strict' => true, - ]]) - ; - $services->set(MbStrFunctionsFixer::class); - $services->set(LinebreakAfterOpeningTagFixer::class); - $services->set(CombineConsecutiveIssetsFixer::class); - $services->set(CombineConsecutiveUnsetsFixer::class); - $services->set(CompactNullableTypehintFixer::class); - $services->set(NoSuperfluousElseifFixer::class); - $services->set(NoSuperfluousPhpdocTagsFixer::class); - $services->set(PhpdocTrimConsecutiveBlankLineSeparationFixer::class); - $services->set(PhpdocOrderFixer::class); - $services->set(SimplifiedNullReturnFixer::class); - $services->set(HeaderCommentFixer::class) - ->call('configure', [[ - 'header' => $header, - ]]) - ; - $services->set(AlignMultilineCommentFixer::class) - ->call('configure', [[ - 'comment_type' => 'all_multiline', - ]]) - ; - $services->set(PhpUnitTestAnnotationFixer::class) - ->call('configure', [[ - 'style' => 'annotation', - ]]) - ; - $services->set(PhpUnitTestCaseStaticMethodCallsFixer::class); - $services->set(GlobalNamespaceImportFixer::class) - ->call('configure', [[ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ]]) - ; + $config->rule(StrictParamFixer::class); + $config->rule(StrictComparisonFixer::class); + $config->rule(ArrayIndentationFixer::class); + $config->rule(OrderedImportsFixer::class); + $config->rule(ProtectedToPrivateFixer::class); + $config->rule(DeclareStrictTypesFixer::class); + $config->rule(NativeConstantInvocationFixer::class); + $config->rule(MbStrFunctionsFixer::class); + $config->rule(LinebreakAfterOpeningTagFixer::class); + $config->rule(CombineConsecutiveIssetsFixer::class); + $config->rule(CombineConsecutiveUnsetsFixer::class); + $config->rule(CompactNullableTypehintFixer::class); + $config->rule(NoSuperfluousElseifFixer::class); + $config->rule(NoSuperfluousPhpdocTagsFixer::class); + $config->rule(PhpdocTrimConsecutiveBlankLineSeparationFixer::class); + $config->rule(PhpdocOrderFixer::class); + $config->rule(SimplifiedNullReturnFixer::class); + $config->rule(PhpUnitTestCaseStaticMethodCallsFixer::class); + $config->ruleWithConfiguration(ArraySyntaxFixer::class, [ + 'syntax' => 'short', + ]); + $config->ruleWithConfiguration(NativeFunctionInvocationFixer::class, [ + 'include' => ['@compiler_optimized'], + 'scope' => 'namespaced', + 'strict' => true, + ]); + $config->ruleWithConfiguration(HeaderCommentFixer::class, [ + 'header' => $header, + ]); + $config->ruleWithConfiguration(AlignMultilineCommentFixer::class, [ + 'comment_type' => 'all_multiline', + ]); + $config->ruleWithConfiguration(PhpUnitTestAnnotationFixer::class, [ + 'style' => 'annotation', + ]); + $config->ruleWithConfiguration(GlobalNamespaceImportFixer::class, [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ]); - $services->remove(GeneralPhpdocAnnotationRemoveFixer::class); - $services->remove(PhpUnitTestClassRequiresCoversFixer::class); - - $parameters = $containerConfigurator->parameters(); - $parameters - ->set(Option::PARALLEL, true) - ->set(Option::PATHS, [__DIR__]) - ->set(Option::SKIP, [__DIR__ . '/.github', __DIR__ . '/doc', __DIR__ . '/vendor']) + $config->services() + ->remove(PhpUnitTestClassRequiresCoversFixer::class) ; + + $config->parallel(); + $config->paths([ + __DIR__.'/src', + __DIR__.'/tests', + ]); }; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6b57145444ff66ce98a9f629da5f55a94433a485..116a7f440b1ceefacc23e1d8a234e43780aa5f15 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,14 @@ </testsuite> </testsuites> <listeners> - <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"/> + <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"> + <arguments> + <array> + <element key="time-sensitive"><string>OTPHP</string></element> + <element key="time-sensitive"><string>OTPHP\TOTP</string></element> + </array> + </arguments> + </listener> + <listener class="Symfony\Bridge\PhpUnit\CoverageListener" /> </listeners> </phpunit> diff --git a/rector.php b/rector.php index 7ccb5c0926b5895dacfd3b8a3e4d1a9e403d73d3..1f7b45d747641fcd7c57709e1bb5f081b5105148 100644 --- a/rector.php +++ b/rector.php @@ -2,33 +2,35 @@ declare(strict_types=1); -use Rector\Core\Configuration\Option; +use Rector\Config\RectorConfig; +use Rector\Core\ValueObject\PhpVersion; use Rector\Doctrine\Set\DoctrineSetList; use Rector\Php74\Rector\Property\TypedPropertyRector; 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 Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -return static function (ContainerConfigurator $containerConfigurator): void { - $containerConfigurator->import(SetList::DEAD_CODE); - $containerConfigurator->import(SetList::PHP_80); - $containerConfigurator->import(SymfonySetList::SYMFONY_52); - $containerConfigurator->import(SymfonySetList::SYMFONY_CODE_QUALITY); - $containerConfigurator->import(SymfonySetList::SYMFONY_52_VALIDATOR_ATTRIBUTES); - $containerConfigurator->import(SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION); - $containerConfigurator->import(SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES); - $containerConfigurator->import(DoctrineSetList::DOCTRINE_CODE_QUALITY); - $containerConfigurator->import(DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES); - $containerConfigurator->import(PHPUnitSetList::PHPUNIT_EXCEPTION); - $containerConfigurator->import(PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD); - $containerConfigurator->import(PHPUnitSetList::PHPUNIT_91); - $containerConfigurator->import(PHPUnitSetList::PHPUNIT_YIELD_DATA_PROVIDER); - $parameters = $containerConfigurator->parameters(); - $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/tests']); - $parameters->set(Option::AUTO_IMPORT_NAMES, true); - $parameters->set(Option::IMPORT_SHORT_CLASSES, true); +return static function (RectorConfig $rectorConfig): void { + $rectorConfig->import(SetList::DEAD_CODE); + $rectorConfig->import(LevelSetList::UP_TO_PHP_81); + $rectorConfig->import(SymfonyLevelSetList::UP_TO_SYMFONY_60); + $rectorConfig->import(SymfonySetList::SYMFONY_CODE_QUALITY); + $rectorConfig->import(SymfonySetList::SYMFONY_52_VALIDATOR_ATTRIBUTES); + $rectorConfig->import(SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION); + $rectorConfig->import(SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES); + $rectorConfig->import(DoctrineSetList::DOCTRINE_CODE_QUALITY); + $rectorConfig->import(DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES); + $rectorConfig->import(PHPUnitSetList::PHPUNIT_EXCEPTION); + $rectorConfig->import(PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD); + $rectorConfig->import(PHPUnitSetList::PHPUNIT_91); + $rectorConfig->import(PHPUnitSetList::PHPUNIT_YIELD_DATA_PROVIDER); + $rectorConfig->paths([__DIR__ . '/src']); + $rectorConfig->phpVersion(PhpVersion::PHP_81); + $rectorConfig->importNames(); + $rectorConfig->importShortClasses(); - $services = $containerConfigurator->services(); + $services = $rectorConfig->services(); $services->set(TypedPropertyRector::class); }; diff --git a/src/Factory.php b/src/Factory.php index eba1a4e73c0b1f3a4f74b03f0223d8ec8a5eac8f..1260c71643c9698fc771c2acc7fc1a6794087934 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -7,7 +7,6 @@ namespace OTPHP; use Assert\Assertion; use function count; use InvalidArgumentException; -use function Safe\sprintf; use Throwable; /** diff --git a/src/HOTP.php b/src/HOTP.php index 573ae463fe677d014c2c41a6785af9ac4059e7d9..9c278c212ddf03c9e43c79f731d2aa84a57c3dc6 100644 --- a/src/HOTP.php +++ b/src/HOTP.php @@ -64,16 +64,13 @@ final class HOTP extends OTP implements HOTPInterface */ protected function getParameterMap(): array { - return array_merge( - parent::getParameterMap(), - [ - 'counter' => static function ($value): int { - Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.'); - - return (int) $value; - }, - ] - ); + return [...parent::getParameterMap(), ...[ + 'counter' => static function ($value): int { + Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.'); + + return (int) $value; + }, + ]]; } private function updateCounter(int $counter): void diff --git a/src/OTP.php b/src/OTP.php index 25fd4d62a98fddd1fa3bc621d22ac4d459ff0077..bd4826f612f444fafc98644ee34a9196f5fbdc45 100644 --- a/src/OTP.php +++ b/src/OTP.php @@ -10,8 +10,6 @@ use function count; use Exception; use ParagonIE\ConstantTime\Base32; use RuntimeException; -use function Safe\ksort; -use function Safe\sprintf; use function Safe\unpack; use const STR_PAD_LEFT; @@ -33,9 +31,9 @@ abstract class OTP implements OTPInterface return str_replace($placeholder, $provisioning_uri, $uri); } - public function at(int $timestamp): string + public function at(int $input): string { - return $this->generateOTP($timestamp); + return $this->generateOTP($input); } /** @@ -80,7 +78,7 @@ abstract class OTP implements OTPInterface $label = $this->getLabel(); Assertion::string($label, 'The label is not set.'); Assertion::false($this->hasColon($label), 'Label must not contain a colon.'); - $options = array_merge($options, $this->getParameters()); + $options = [...$options, ...$this->getParameters()]; $this->filterOptions($options); $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options)); diff --git a/src/OTPInterface.php b/src/OTPInterface.php index 38f1db4a229fb871f65b4a1ea265e1227d478dea..072e8212febf698af77319516a0df6f8ca4a35fc 100644 --- a/src/OTPInterface.php +++ b/src/OTPInterface.php @@ -9,7 +9,7 @@ interface OTPInterface /** * @return string Return the OTP at the specified timestamp */ - public function at(int $timestamp): string; + public function at(int $input): string; /** * Verify that the OTP is valid with the specified input. If no input is provided, the input is set to a default diff --git a/src/ParameterTrait.php b/src/ParameterTrait.php index cbc3e277a575749d4bf880c8e52472b8f369d18a..ddefb983df3a925eb63e2c0cd414ceffa44ac786 100644 --- a/src/ParameterTrait.php +++ b/src/ParameterTrait.php @@ -8,7 +8,6 @@ use function array_key_exists; use Assert\Assertion; use InvalidArgumentException; use ParagonIE\ConstantTime\Base32; -use function Safe\sprintf; trait ParameterTrait { diff --git a/src/TOTP.php b/src/TOTP.php index 2ac22502ae390df89194f97e4ad071c209d4254f..316ccb5e76a3c15da1bf5e82e487cef4df6b5ef2 100644 --- a/src/TOTP.php +++ b/src/TOTP.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace OTPHP; use Assert\Assertion; -use function Safe\ksort; final class TOTP extends OTP implements TOTPInterface { @@ -49,9 +48,9 @@ final class TOTP extends OTP implements TOTPInterface return $period - (time() % $this->getPeriod()); } - public function at(int $timestamp): string + public function at(int $input): string { - return $this->generateOTP($this->timecode($timestamp)); + return $this->generateOTP($this->timecode($input)); } public function now(): string @@ -65,7 +64,7 @@ final class TOTP extends OTP implements TOTPInterface */ public function verify(string $otp, null|int $timestamp = null, null|int $leeway = null): bool { - $timestamp = $timestamp ?? time(); + $timestamp ??= time(); Assertion::greaterOrEqualThan($timestamp, 0, 'Timestamp must be at least 0.'); if ($leeway === null) { diff --git a/src/Url.php b/src/Url.php index 3fe7a8da6295804e990f78d53517061a53ffa6d6..81cebfc5674e22913d78ba477ce55cd32ef4a2e5 100644 --- a/src/Url.php +++ b/src/Url.php @@ -13,12 +13,12 @@ use function Safe\parse_url; final class Url { public function __construct( - private string $scheme, - private string $host, - private string $path, - private string $secret, + private readonly string $scheme, + private readonly string $host, + private readonly string $path, + private readonly string $secret, /** @var array<string, mixed> $query */ - private array $query + private readonly array $query ) { } @@ -64,7 +64,7 @@ final class Url $path = $parsed_url['path']; $query = $parsed_url['query']; $parsedQuery = []; - parse_str($query, $parsedQuery); + parse_str((string) $query, $parsedQuery); Assertion::keyExists($parsedQuery, 'secret', 'Not a valid OTP provisioning URI'); $secret = $parsedQuery['secret']; unset($parsedQuery['secret']); diff --git a/tests/TOTPTest.php b/tests/TOTPTest.php index 497105efd4680bbf66e547072beb915c7bc30477..289c26b92db69939f97d19e26e496967930d564a 100644 --- a/tests/TOTPTest.php +++ b/tests/TOTPTest.php @@ -15,7 +15,6 @@ use Symfony\Bridge\PhpUnit\ClockMock; /** * @internal - * @group time-sensitive */ final class TOTPTest extends TestCase { @@ -168,10 +167,11 @@ final class TOTPTest extends TestCase */ public function verifyOtpNow(): void { + $time = time(); $otp = $this->createTOTP(6, 'sha1', 30); - $totp = $otp->at(time()); - static::assertTrue($otp->verify($totp)); + $totp = $otp->at($time); + static::assertTrue($otp->verify($totp, $time)); } /**