diff --git a/Makefile b/Makefile index bf8a8af..529b80e 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ci-rector: vendor ## Check all files using Rector (CI/CD) vendor/bin/rector process --ansi --dry-run ci-mu: vendor ## Mutation tests (CI/CD) - vendor/bin/infection --logger-github --git-diff-filter=AM -s --threads=$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance" + vendor/bin/infection --logger-github -s --threads=$$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance" ######################## # Everyday # @@ -44,7 +44,7 @@ rector: vendor ## Check all files using Rector ######################## mu: vendor ## Mutation tests - vendor/bin/infection -s --threads=$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance" + vendor/bin/infection -s --threads=$$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance" cc: vendor ## Show test coverage rates (HTML) vendor/bin/phpunit --coverage-html ./build diff --git a/composer.json b/composer.json index 7102414..bff24fa 100644 --- a/composer.json +++ b/composer.json @@ -18,23 +18,19 @@ "require": { "php": "^8.1", "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.0", - "beberlei/assert": "^3.0", - "thecodingmachine/safe": "^1.0|^2.0" + "paragonie/constant_time_encoding": "^2.0" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0", "infection/infection": "^0.26", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-beberlei-assert": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13", + "rector/rector": "^0.14", "symfony/phpunit-bridge": "^6.0", - "symplify/easy-coding-standard": "^11.0", - "thecodingmachine/phpstan-safe-rule": "^1.0" + "symplify/easy-coding-standard": "^11.0" }, "autoload": { "psr-4": { "OTPHP\\": "src/" } diff --git a/phpstan.neon b/phpstan.neon index 53210dd..9cb0a82 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,7 +10,5 @@ includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-deprecation-rules/rules.neon - - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon - - vendor/phpstan/phpstan-beberlei-assert/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - vendor/ekino/phpstan-banned-code/extension.neon diff --git a/src/Factory.php b/src/Factory.php index b904c39..dc98c76 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -4,7 +4,6 @@ namespace OTPHP; -use Assert\Assertion; use function count; use InvalidArgumentException; use Throwable; @@ -20,7 +19,7 @@ public static function loadFromProvisioningUri(string $uri): OTPInterface { try { $parsed_url = Url::fromString($uri); - Assertion::eq('otpauth', $parsed_url->getScheme()); + $parsed_url->getScheme() === 'otpauth' || throw new InvalidArgumentException('Invalid scheme.'); } catch (Throwable $throwable) { throw new InvalidArgumentException('Not a valid OTP provisioning URI', $throwable->getCode(), $throwable); } @@ -51,7 +50,9 @@ private static function populateOTP(OTPInterface $otp, Url $data): void } if ($otp->getIssuer() !== null) { - Assertion::eq($result[0], $otp->getIssuer(), 'Invalid OTP: invalid issuer in parameter'); + $result[0] === $otp->getIssuer() || throw new InvalidArgumentException( + 'Invalid OTP: invalid issuer in parameter' + ); $otp->setIssuerIncludedAsParameter(true); } $otp->setIssuer($result[0]); diff --git a/src/HOTP.php b/src/HOTP.php index 98a3ac8..5336a60 100644 --- a/src/HOTP.php +++ b/src/HOTP.php @@ -4,7 +4,8 @@ namespace OTPHP; -use Assert\Assertion; +use InvalidArgumentException; +use function is_int; /** * @see \OTPHP\Test\HOTPTest @@ -29,7 +30,7 @@ public static function create( public function getCounter(): int { $value = $this->getParameter('counter'); - Assertion::integer($value, 'Invalid "counter" parameter.'); + is_int($value) || throw new InvalidArgumentException('Invalid "counter" parameter.'); return $value; } @@ -46,7 +47,7 @@ public function getProvisioningUri(): string */ public function verify(string $otp, null|int $counter = null, null|int $window = null): bool { - Assertion::greaterOrEqualThan($counter, 0, 'The counter must be at least 0.'); + $counter >= 0 || throw new InvalidArgumentException('The counter must be at least 0.'); if ($counter === null) { $counter = $this->getCounter(); @@ -69,7 +70,7 @@ protected function getParameterMap(): array { return [...parent::getParameterMap(), ...[ 'counter' => static function ($value): int { - Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.'); + (int) $value >= 0 || throw new InvalidArgumentException('Counter must be at least 0.'); return (int) $value; }, diff --git a/src/OTP.php b/src/OTP.php index bd4826f..f590668 100644 --- a/src/OTP.php +++ b/src/OTP.php @@ -4,13 +4,13 @@ namespace OTPHP; -use Assert\Assertion; use function chr; use function count; use Exception; +use InvalidArgumentException; +use function is_string; use ParagonIE\ConstantTime\Base32; use RuntimeException; -use function Safe\unpack; use const STR_PAD_LEFT; abstract class OTP implements OTPInterface @@ -42,11 +42,12 @@ public function at(int $input): string protected function generateOTP(int $input): string { $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true); - - $hmac = array_values(unpack('C*', $hash)); + $unpacked = unpack('C*', $hash); + $unpacked !== false || throw new InvalidArgumentException('Invalid data.'); + $hmac = array_values($unpacked); $offset = ($hmac[count($hmac) - 1] & 0xF); - $code = ($hmac[$offset + 0] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF); + $code = ($hmac[$offset] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF); $otp = $code % (10 ** $this->getDigits()); return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT); @@ -76,8 +77,8 @@ protected function filterOptions(array &$options): void protected function generateURI(string $type, array $options): string { $label = $this->getLabel(); - Assertion::string($label, 'The label is not set.'); - Assertion::false($this->hasColon($label), 'Label must not contain a colon.'); + is_string($label) || throw new InvalidArgumentException('The label is not set.'); + $this->hasColon($label) === false || throw new InvalidArgumentException('Label must not contain a colon.'); $options = [...$options, ...$this->getParameters()]; $this->filterOptions($options); $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options)); diff --git a/src/ParameterTrait.php b/src/ParameterTrait.php index ddefb98..cef50f3 100644 --- a/src/ParameterTrait.php +++ b/src/ParameterTrait.php @@ -5,8 +5,10 @@ namespace OTPHP; use function array_key_exists; -use Assert\Assertion; +use function in_array; use InvalidArgumentException; +use function is_int; +use function is_string; use ParagonIE\ConstantTime\Base32; trait ParameterTrait @@ -39,7 +41,7 @@ public function getParameters(): array public function getSecret(): string { $value = $this->getParameter('secret'); - Assertion::string($value, 'Invalid "secret" parameter.'); + is_string($value) || throw new InvalidArgumentException('Invalid "secret" parameter.'); return $value; } @@ -77,7 +79,7 @@ public function setIssuerIncludedAsParameter(bool $issuer_included_as_parameter) public function getDigits(): int { $value = $this->getParameter('digits'); - Assertion::integer($value, 'Invalid "digits" parameter.'); + is_int($value) || throw new InvalidArgumentException('Invalid "digits" parameter.'); return $value; } @@ -85,7 +87,7 @@ public function getDigits(): int public function getDigest(): string { $value = $this->getParameter('algorithm'); - Assertion::string($value, 'Invalid "algorithm" parameter.'); + is_string($value) || throw new InvalidArgumentException('Invalid "algorithm" parameter.'); return $value; } @@ -127,7 +129,9 @@ protected function getParameterMap(): array { return [ 'label' => function ($value) { - Assertion::false($this->hasColon($value), 'Label must not contain a colon.'); + $this->hasColon($value) === false || throw new InvalidArgumentException( + 'Label must not contain a colon.' + ); return $value; }, @@ -140,17 +144,22 @@ protected function getParameterMap(): array }, 'algorithm' => static function ($value): string { $value = mb_strtolower($value); - Assertion::inArray($value, hash_algos(), sprintf('The "%s" digest is not supported.', $value)); + in_array($value, hash_algos(), true) || throw new InvalidArgumentException(sprintf( + 'The "%s" digest is not supported.', + $value + )); return $value; }, 'digits' => static function ($value): int { - Assertion::greaterThan($value, 0, 'Digits must be at least 1.'); + $value > 0 || throw new InvalidArgumentException('Digits must be at least 1.'); return (int) $value; }, 'issuer' => function ($value) { - Assertion::false($this->hasColon($value), 'Issuer must not contain a colon.'); + $this->hasColon($value) === false || throw new InvalidArgumentException( + 'Issuer must not contain a colon.' + ); return $value; }, diff --git a/src/TOTP.php b/src/TOTP.php index 881a1af..2e67367 100644 --- a/src/TOTP.php +++ b/src/TOTP.php @@ -4,7 +4,8 @@ namespace OTPHP; -use Assert\Assertion; +use InvalidArgumentException; +use function is_int; /** * @see \OTPHP\Test\TOTPTest @@ -31,7 +32,7 @@ public static function create( public function getPeriod(): int { $value = $this->getParameter('period'); - Assertion::integer($value, 'Invalid "period" parameter.'); + is_int($value) || throw new InvalidArgumentException('Invalid "period" parameter.'); return $value; } @@ -39,7 +40,7 @@ public function getPeriod(): int public function getEpoch(): int { $value = $this->getParameter('epoch'); - Assertion::integer($value, 'Invalid "epoch" parameter.'); + is_int($value) || throw new InvalidArgumentException('Invalid "epoch" parameter.'); return $value; } @@ -68,14 +69,16 @@ public function now(): string public function verify(string $otp, null|int $timestamp = null, null|int $leeway = null): bool { $timestamp ??= time(); - Assertion::greaterOrEqualThan($timestamp, 0, 'Timestamp must be at least 0.'); + $timestamp >= 0 || throw new InvalidArgumentException('Timestamp must be at least 0.'); if ($leeway === null) { return $this->compareOTP($this->at($timestamp), $otp); } $leeway = abs($leeway); - Assertion::lessThan($leeway, $this->getPeriod(), 'The leeway must be lower than the TOTP period'); + $leeway < $this->getPeriod() || throw new InvalidArgumentException( + 'The leeway must be lower than the TOTP period' + ); return $this->compareOTP($this->at($timestamp - $leeway), $otp) || $this->compareOTP($this->at($timestamp), $otp) @@ -110,12 +113,14 @@ protected function getParameterMap(): array parent::getParameterMap(), [ 'period' => static function ($value): int { - Assertion::greaterThan((int) $value, 0, 'Period must be at least 1.'); + (int) $value > 0 || throw new InvalidArgumentException('Period must be at least 1.'); return (int) $value; }, 'epoch' => static function ($value): int { - Assertion::greaterOrEqualThan((int) $value, 0, 'Epoch must be greater than or equal to 0.'); + (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 81cebfc..f3dce71 100644 --- a/src/Url.php +++ b/src/Url.php @@ -4,8 +4,9 @@ namespace OTPHP; -use Assert\Assertion; -use function Safe\parse_url; +use function array_key_exists; +use InvalidArgumentException; +use function is_string; /** * @internal @@ -53,19 +54,26 @@ public function getQuery(): array public static function fromString(string $uri): self { $parsed_url = parse_url($uri); - Assertion::isArray($parsed_url, 'Not a valid OTP provisioning URI'); + $parsed_url !== false || throw new InvalidArgumentException('Invalid URI.'); foreach (['scheme', 'host', 'path', 'query'] as $key) { - Assertion::keyExists($parsed_url, $key, 'Not a valid OTP provisioning URI'); - Assertion::string($parsed_url[$key], 'Not a valid OTP provisioning URI'); + array_key_exists($key, $parsed_url) || throw new InvalidArgumentException( + 'Not a valid OTP provisioning URI' + ); + is_string($parsed_url[$key]) || throw new InvalidArgumentException('Not a valid OTP provisioning URI'); } - $scheme = $parsed_url['scheme']; - Assertion::eq('otpauth', $scheme, 'Not a valid OTP provisioning URI'); - $host = $parsed_url['host']; - $path = $parsed_url['path']; - $query = $parsed_url['query']; + $scheme = $parsed_url['scheme'] ?? null; + $host = $parsed_url['host'] ?? null; + $path = $parsed_url['path'] ?? null; + $query = $parsed_url['query'] ?? null; + $scheme === 'otpauth' || throw new InvalidArgumentException('Not a valid OTP provisioning URI'); + is_string($host) || throw new InvalidArgumentException('Invalid URI.'); + is_string($path) || throw new InvalidArgumentException('Invalid URI.'); + is_string($query) || throw new InvalidArgumentException('Invalid URI.'); $parsedQuery = []; - parse_str((string) $query, $parsedQuery); - Assertion::keyExists($parsedQuery, 'secret', 'Not a valid OTP provisioning URI'); + parse_str($query, $parsedQuery); + array_key_exists('secret', $parsedQuery) || throw new InvalidArgumentException( + 'Not a valid OTP provisioning URI' + ); $secret = $parsedQuery['secret']; unset($parsedQuery['secret']); diff --git a/tests/HOTPTest.php b/tests/HOTPTest.php index 4026de2..b0acca0 100644 --- a/tests/HOTPTest.php +++ b/tests/HOTPTest.php @@ -4,7 +4,6 @@ namespace OTPHP\Test; -use Assert\Assertion; use InvalidArgumentException; use OTPHP\HOTP; use PHPUnit\Framework\TestCase; @@ -188,7 +187,6 @@ private function createHOTP( $otp = HOTP::create($secret, $counter, $digest, $digits); $otp->setLabel($label); $otp->setIssuer($issuer); - Assertion::isInstanceOf($otp, HOTP::class); return $otp; } diff --git a/tests/TOTPTest.php b/tests/TOTPTest.php index 01a2bdf..922deb0 100644 --- a/tests/TOTPTest.php +++ b/tests/TOTPTest.php @@ -4,7 +4,6 @@ namespace OTPHP\Test; -use Assert\Assertion; use InvalidArgumentException; use OTPHP\TOTP; use OTPHP\TOTPInterface; @@ -406,7 +405,6 @@ private function createTOTP( $otp = TOTP::create($secret, $period, $digest, $digits, $epoch); $otp->setLabel($label); $otp->setIssuer($issuer); - Assertion::isInstanceOf($otp, TOTP::class); return $otp; }