diff --git a/src/Configuration/Configuration.php b/src/Configuration/Configuration.php index b8e974f55..f920620a4 100644 --- a/src/Configuration/Configuration.php +++ b/src/Configuration/Configuration.php @@ -33,6 +33,7 @@ use KevinGH\Box\Json\Json; use KevinGH\Box\MapFile; use KevinGH\Box\Phar\CompressionAlgorithm; +use KevinGH\Box\Phar\SigningAlgorithm; use KevinGH\Box\PhpScoper\SerializableScoper; use Phar; use phpDocumentor\Reflection\DocBlockFactory; @@ -58,7 +59,6 @@ use function array_walk; use function constant; use function current; -use function defined; use function dirname; use function explode; use function file_exists; @@ -77,7 +77,6 @@ use function iter\toArray; use function iter\values; use function KevinGH\Box\get_box_version; -use function KevinGH\Box\get_phar_signing_algorithms; use function KevinGH\Box\unique_id; use function krsort; use function preg_match; @@ -110,7 +109,7 @@ final class Configuration 'finder', ]; private const PHP_SCOPER_CONFIG = 'scoper.inc.php'; - private const DEFAULT_SIGNING_ALGORITHM = Phar::SHA1; + private const DEFAULT_SIGNING_ALGORITHM = SigningAlgorithm::SHA1; private const DEFAULT_ALIAS_PREFIX = 'box-auto-generated-alias-'; private const DEFAULT_IGNORED_ANNOTATIONS = [ @@ -406,7 +405,7 @@ public static function create(?string $file, stdClass $raw): self * @param bool $promptForPrivateKey If the user should be prompted for the private key passphrase * @param array $processedReplacements The processed list of replacement placeholders and their values * @param null|non-empty-string $shebang The shebang line - * @param int $signingAlgorithm The PHAR siging algorithm. See \Phar constants + * @param SigningAlgorithm $signingAlgorithm The PHAR siging algorithm. See \Phar constants * @param null|string $stubBannerContents The stub banner comment * @param null|string $stubBannerPath The path to the stub banner comment file * @param null|string $stubPath The PHAR stub file path @@ -443,7 +442,7 @@ private function __construct( private bool $promptForPrivateKey, private array $processedReplacements, private ?string $shebang, - private int|string $signingAlgorithm, + private SigningAlgorithm $signingAlgorithm, private ?string $stubBannerContents, private ?string $stubBannerPath, private ?string $stubPath, @@ -650,7 +649,7 @@ public function getShebang(): ?string return $this->shebang; } - public function getSigningAlgorithm(): int + public function getSigningAlgorithm(): SigningAlgorithm { return $this->signingAlgorithm; } @@ -1929,10 +1928,10 @@ private static function retrieveOutputPath( private static function retrievePrivateKeyPath( stdClass $raw, string $basePath, - int $signingAlgorithm, + SigningAlgorithm $signingAlgorithm, ConfigurationLogger $logger, ): ?string { - if (property_exists($raw, self::KEY_KEY) && Phar::OPENSSL !== $signingAlgorithm) { + if (property_exists($raw, self::KEY_KEY) && SigningAlgorithm::OPENSSL !== $signingAlgorithm) { if (null === $raw->{self::KEY_KEY}) { $logger->addRecommendation( 'The setting "key" has been set but is unnecessary since the signing algorithm is not "OPENSSL".', @@ -1948,7 +1947,7 @@ private static function retrievePrivateKeyPath( if (!isset($raw->{self::KEY_KEY})) { Assert::true( - Phar::OPENSSL !== $signingAlgorithm, + SigningAlgorithm::OPENSSL !== $signingAlgorithm, 'Expected to have a private key for OpenSSL signing but none have been provided.', ); @@ -1964,7 +1963,7 @@ private static function retrievePrivateKeyPath( private static function retrievePrivateKeyPassphrase( stdClass $raw, - int $algorithm, + SigningAlgorithm $algorithm, ConfigurationLogger $logger, ): ?string { self::checkIfDefaultValue($logger, $raw, self::KEY_PASS_KEY); @@ -1976,7 +1975,7 @@ private static function retrievePrivateKeyPassphrase( /** @var null|false|string $keyPass */ $keyPass = $raw->{self::KEY_PASS_KEY}; - if (Phar::OPENSSL !== $algorithm) { + if (SigningAlgorithm::OPENSSL !== $algorithm) { if (false === $keyPass || null === $keyPass) { $logger->addRecommendation( sprintf( @@ -2264,7 +2263,7 @@ private static function retrieveShebang(stdClass $raw, bool $stubIsGenerated, Co return $shebang; } - private static function retrieveSigningAlgorithm(stdClass $raw, ConfigurationLogger $logger): int + private static function retrieveSigningAlgorithm(stdClass $raw, ConfigurationLogger $logger): SigningAlgorithm { if (property_exists($raw, self::ALGORITHM_KEY) && null === $raw->{self::ALGORITHM_KEY}) { self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY); @@ -2274,19 +2273,8 @@ private static function retrieveSigningAlgorithm(stdClass $raw, ConfigurationLog return self::DEFAULT_SIGNING_ALGORITHM; } - $algorithm = mb_strtoupper($raw->{self::ALGORITHM_KEY}); - - Assert::inArray($algorithm, array_keys(get_phar_signing_algorithms())); - - Assert::true( - defined('Phar::'.$algorithm), - sprintf( - 'The signing algorithm "%s" is not supported by your current PHAR version.', - $algorithm, - ), - ); - - $algorithm = constant('Phar::'.$algorithm); + $algorithmLabel = mb_strtoupper($raw->{self::ALGORITHM_KEY}); + $algorithm = SigningAlgorithm::fromLabel($algorithmLabel); if (self::DEFAULT_SIGNING_ALGORITHM === $algorithm) { self::addRecommendationForDefaultValue($logger, self::ALGORITHM_KEY); @@ -2424,11 +2412,11 @@ private static function retrieveInterceptsFileFunctions( private static function retrievePromptForPrivateKey( stdClass $raw, - int $signingAlgorithm, + SigningAlgorithm $signingAlgorithm, ConfigurationLogger $logger, ): bool { if (isset($raw->{self::KEY_PASS_KEY}) && true === $raw->{self::KEY_PASS_KEY}) { - if (Phar::OPENSSL !== $signingAlgorithm) { + if (SigningAlgorithm::OPENSSL !== $signingAlgorithm) { $logger->addWarning( 'A prompt for password for the private key has been requested but ignored since the signing ' .'algorithm used is not "OPENSSL.', diff --git a/src/Configuration/ExportableConfiguration.php b/src/Configuration/ExportableConfiguration.php index 620ba3147..2bc2ff595 100644 --- a/src/Configuration/ExportableConfiguration.php +++ b/src/Configuration/ExportableConfiguration.php @@ -23,10 +23,8 @@ use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\CliDumper; -use function array_flip; use function array_map; use function iter\values; -use function KevinGH\Box\get_phar_signing_algorithms; use function sort; use const SORT_STRING; @@ -84,7 +82,7 @@ public static function create(Configuration $configuration): self $configuration->promptForPrivateKey(), $configuration->getReplacements(), $configuration->getShebang(), - array_flip(get_phar_signing_algorithms())[$configuration->getSigningAlgorithm()], + $configuration->getSigningAlgorithm()->name, $configuration->getStubBannerContents(), $normalizePath($configuration->getStubBannerPath()), $normalizePath($configuration->getStubPath()), diff --git a/src/Console/Command/Compile.php b/src/Console/Command/Compile.php index e581e481e..5958b249a 100644 --- a/src/Console/Command/Compile.php +++ b/src/Console/Command/Compile.php @@ -763,7 +763,7 @@ private static function signPhar( if (null === $key) { $box->getPhar()->setSignatureAlgorithm( - $config->getSigningAlgorithm(), + $config->getSigningAlgorithm()->value, ); return; diff --git a/src/Phar/SigningAlgorithm.php b/src/Phar/SigningAlgorithm.php new file mode 100644 index 000000000..a8bd4a610 --- /dev/null +++ b/src/Phar/SigningAlgorithm.php @@ -0,0 +1,75 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Phar; + +use Phar; +use UnexpectedValueException; +use function array_keys; +use function array_search; + +/** + * The required extension to execute the PHAR now that it is compressed. + * + * This is a tiny wrapper around the PHAR compression algorithm + * to make it a bit more type-safe and convenient to work with. + * + * @private + */ +enum SigningAlgorithm: int +{ + case MD5 = Phar::MD5; + case SHA1 = Phar::SHA1; + case SHA256 = Phar::SHA256; + case SHA512 = Phar::SHA512; + case OPENSSL = Phar::OPENSSL; + + private const LABELS = [ + 'MD5' => Phar::MD5, + 'SHA1' => Phar::SHA1, + 'SHA256' => Phar::SHA256, + 'SHA512' => Phar::SHA512, + 'OPENSSL' => Phar::OPENSSL, + ]; + + /** + * @return list + */ + public static function getLabels(): array + { + return array_keys(self::LABELS); + } + + public static function fromLabel(string $label): self + { + return match ($label) { + 'MD5' => self::MD5, + 'SHA1' => self::SHA1, + 'SHA256' => self::SHA256, + 'SHA512' => self::SHA512, + 'OPENSSL' => self::OPENSSL, + default => throw new UnexpectedValueException( + sprintf( + 'The signing algorithm "%s" is not supported by your current PHAR version.', + $label, + ), + ), + }; + } + + public function getLabel(): string + { + return array_search($this, self::LABELS, true); + } +} diff --git a/src/functions.php b/src/functions.php index 7c8b755f4..8ff722dc1 100644 --- a/src/functions.php +++ b/src/functions.php @@ -71,8 +71,7 @@ function get_box_version(): string } /** - * TODO: to remove once up update PHP-Scoper. - * + * @deprecated since 4.3.0. Use \KevinGH\Box\Phar\CompressionAlgorithm instead. * @private * * @return array @@ -89,6 +88,8 @@ function get_phar_compression_algorithms(): array } /** + * @deprecated Since 4.5.0. Use \KevinGH\Box\Phar\SigningAlgorithm instead. + * * @private * * @return array diff --git a/tests/Configuration/ConfigurationSigningTest.php b/tests/Configuration/ConfigurationSigningTest.php index 91e6b38e4..8a0bd9f35 100644 --- a/tests/Configuration/ConfigurationSigningTest.php +++ b/tests/Configuration/ConfigurationSigningTest.php @@ -16,7 +16,8 @@ use Fidry\FileSystem\FS; use InvalidArgumentException; -use Phar; +use KevinGH\Box\Phar\SigningAlgorithm; +use UnexpectedValueException; use function array_unshift; use function in_array; use const DIRECTORY_SEPARATOR; @@ -33,7 +34,7 @@ class ConfigurationSigningTest extends ConfigurationTestCase { public function test_the_default_signing_is_sha1(): void { - self::assertSame(Phar::SHA1, $this->config->getSigningAlgorithm()); + self::assertSame(SigningAlgorithm::SHA1, $this->config->getSigningAlgorithm()); self::assertNull($this->config->getPrivateKeyPath()); self::assertNull($this->config->getPrivateKeyPassphrase()); @@ -69,7 +70,7 @@ public function test_a_recommendation_is_given_if_the_configured_algorithm_is_th /** * @dataProvider passFileFreeSigningAlgorithmProvider */ - public function test_the_signing_algorithm_can_be_configured(string $algorithm, int $expected): void + public function test_the_signing_algorithm_can_be_configured(string $algorithm, SigningAlgorithm $expected): void { $this->setConfig([ 'algorithm' => $algorithm, @@ -85,18 +86,15 @@ public function test_the_signing_algorithm_can_be_configured(string $algorithm, public function test_the_signing_algorithm_provided_must_be_valid(): void { - try { - $this->setConfig([ - 'algorithm' => 'INVALID', - ]); + $this->expectExceptionObject( + new UnexpectedValueException( + 'The signing algorithm "INVALID" is not supported by your current PHAR version.', + ), + ); - self::fail('Expected exception to be thrown.'); - } catch (InvalidArgumentException $exception) { - self::assertSame( - 'Expected one of: "MD5", "SHA1", "SHA256", "SHA512", "OPENSSL". Got: "INVALID"', - $exception->getMessage(), - ); - } + $this->setConfig([ + 'algorithm' => 'INVALID', + ]); } public function test_the_openssl_algorithm_requires_a_private_key(): void @@ -302,9 +300,9 @@ public function test_the_key_pass_can_be_configured(): void public static function passFileFreeSigningAlgorithmProvider(): iterable { - yield ['MD5', Phar::MD5]; - yield ['SHA1', Phar::SHA1]; - yield ['SHA256', Phar::SHA256]; - yield ['SHA512', Phar::SHA512]; + yield ['MD5', SigningAlgorithm::MD5]; + yield ['SHA1', SigningAlgorithm::SHA1]; + yield ['SHA256', SigningAlgorithm::SHA256]; + yield ['SHA512', SigningAlgorithm::SHA512]; } } diff --git a/tests/Configuration/ConfigurationTest.php b/tests/Configuration/ConfigurationTest.php index 2aa38102e..8e8173add 100644 --- a/tests/Configuration/ConfigurationTest.php +++ b/tests/Configuration/ConfigurationTest.php @@ -24,6 +24,7 @@ use KevinGH\Box\Json\JsonValidationException; use KevinGH\Box\MapFile; use KevinGH\Box\Phar\CompressionAlgorithm; +use KevinGH\Box\Phar\SigningAlgorithm; use KevinGH\Box\VarDumperNormalizer; use Phar; use RuntimeException; @@ -2932,7 +2933,7 @@ public function test_it_can_be_created_with_only_default_values(): void self::assertNull($this->config->getPrivateKeyPath()); self::assertSame([], $this->config->getReplacements()); self::assertSame('#!/usr/bin/env php', $this->config->getShebang()); - self::assertSame(Phar::SHA1, $this->config->getSigningAlgorithm()); + self::assertSame(SigningAlgorithm::SHA1, $this->config->getSigningAlgorithm()); $version = self::$version; diff --git a/tests/Phar/SigningAlgorithmTest.php b/tests/Phar/SigningAlgorithmTest.php new file mode 100644 index 000000000..b9ebd9dbb --- /dev/null +++ b/tests/Phar/SigningAlgorithmTest.php @@ -0,0 +1,82 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Phar; + +use PHPUnit\Framework\TestCase; +use ReflectionEnum; +use ReflectionEnumBackedCase; +use UnexpectedValueException; +use function array_map; + +/** + * @covers \KevinGH\Box\Phar\SigningAlgorithm + * + * @internal + */ +final class SigningAlgorithmTest extends TestCase +{ + /** + * @dataProvider signingAlgorithmLabelsProvider + */ + public function test_it_can_tell_be_created_from_its_label( + ?string $label, + SigningAlgorithm $expected + ): void { + $actual = SigningAlgorithm::fromLabel($label); + + self::assertSame($expected, $actual); + + if (null !== $label) { + self::assertSame($label, $actual->name); + } + } + + public static function signingAlgorithmLabelsProvider(): iterable + { + $signingAlgorithmReflection = new ReflectionEnum(SigningAlgorithm::class); + + foreach ($signingAlgorithmReflection->getCases() as $signingAlgorithmCase) { + yield [ + $signingAlgorithmCase->getName(), + $signingAlgorithmCase->getValue(), + ]; + } + } + + public function test_it_can_list_all_its_labels(): void + { + $signingAlgorithmReflection = new ReflectionEnum(SigningAlgorithm::class); + + $expected = array_map( + static fn (ReflectionEnumBackedCase $signingAlgorithmCase) => $signingAlgorithmCase->getName(), + $signingAlgorithmReflection->getCases(), + ); + + $actual = SigningAlgorithm::getLabels(); + + self::assertSame($expected, $actual); + } + + public function test_it_cannot_create_an_unsupported_signing_algorithm(): void + { + $this->expectExceptionObject( + new UnexpectedValueException( + 'The signing algorithm "UNKNOWN" is not supported by your current PHAR version.', + ), + ); + + SigningAlgorithm::fromLabel('UNKNOWN'); + } +}