diff --git a/src/Plugin.php b/src/Plugin.php index c6a977e..c8ba9dc 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -48,10 +48,13 @@ public static function getSubscribedEvents(): array */ public function preFileDownload(PreFileDownloadEvent $event): void { - $context = $event->getContext(); + $type = $event->getType(); + $repository = $this->getRepositoryFromEvent($event); - if ($event->getType() === 'metadata' && $context['repository'] instanceof TufValidatedComposerRepository) { - $context['repository']->prepareComposerMetadata($event); + if ($type === 'metadata') { + $repository?->prepareComposerMetadata($event); + } elseif ($type === 'package') { + $repository?->preparePackage($event->getContext()); } } @@ -68,46 +71,44 @@ public function preFileDownload(PreFileDownloadEvent $event): void public function postFileDownload(PostFileDownloadEvent $event): void { $type = $event->getType(); - /** @var array|PackageInterface $context */ $context = $event->getContext(); + $repository = $this->getRepositoryFromEvent($event); if ($type === 'metadata') { - if ($context['repository'] instanceof TufValidatedComposerRepository) { - $context['repository']->validateComposerMetadata($event->getUrl(), $context['response']); - } + $repository?->validateComposerMetadata($event->getUrl(), $context['response']); } elseif ($type === 'package') { - // The repository URL is saved in the package's transport options so that - // it will persist even when loaded from the lock file. - // @see \Tuf\ComposerIntegration\TufValidatedComposerRepository::configurePackageTransportOptions() - $options = $context->getTransportOptions(); - if (array_key_exists('tuf', $options)) { - $repository = $this->getRepositoryByUrl($options['tuf']['repository']); - if ($repository) { - $repository->validatePackage($context, $event->getFileName()); - } - } + $repository?->validatePackage($context, $event->getFileName()); } } - /** - * Looks up a TUF-validated Composer repository by its URL. - * - * @param string $url - * The repository URL. - * @return TufValidatedComposerRepository|null - * The TUF-validated Composer repository with the given URL, or NULL if none - * is currently registered. - */ - private function getRepositoryByUrl(string $url): ?TufValidatedComposerRepository + private function getRepositoryFromEvent(PreFileDownloadEvent|PostFileDownloadEvent $event): ?TufValidatedComposerRepository { - foreach ($this->repositoryManager->getRepositories() as $repository) { - if ($repository instanceof TufValidatedComposerRepository) { - $config = $repository->getRepoConfig(); - if ($config['url'] === $url) { - return $repository; + $type = $event->getType(); + $context = $event->getContext(); + + if ($type === 'metadata') { + $repository = $context['repository']; + return $repository instanceof TufValidatedComposerRepository ? $repository : null; + } + + if ($type === 'package') { + // The repository URL is saved in the package's transport options so that it will persist even when loaded + // from the lock file. + // @see \Tuf\ComposerIntegration\TufValidatedComposerRepository::configurePackageTransportOptions() + $options = $context->getTransportOptions(); + if (empty($options['tuf'])) { + return null; + } + foreach ($this->repositoryManager->getRepositories() as $repository) { + if ($repository instanceof TufValidatedComposerRepository) { + ['url' => $url] = $repository->getRepoConfig(); + if ($url === $options['tuf']['repository']) { + return $repository; + } } } } + return null; } diff --git a/src/TufValidatedComposerRepository.php b/src/TufValidatedComposerRepository.php index e16bebb..0111c81 100644 --- a/src/TufValidatedComposerRepository.php +++ b/src/TufValidatedComposerRepository.php @@ -157,17 +157,14 @@ protected function configurePackageTransportOptions(PackageInterface $package): parent::configurePackageTransportOptions($package); $options = $package->getTransportOptions(); - $config = $this->getRepoConfig(); - // Store the information identifying this package to TUF in a format - // that can be safely saved to and loaded from the lock file. - // @see \Tuf\ComposerIntegration\Plugin::postFileDownload() + ['url' => $url] = $this->getRepoConfig(); + + // Store the information identifying this package to TUF in a form that can be stored in the lock file. + // @see \Tuf\ComposerIntegration\Plugin::getRepositoryFromEvent() $options['tuf'] = [ - 'repository' => $config['url'], + 'repository' => $url, 'target' => $package->getName() . '/' . $package->getVersion(), ]; - if ($this->isTufEnabled($package)) { - $options['max_file_size'] = $this->updater->getLength($options['tuf']['target']); - } $package->setTransportOptions($options); } @@ -260,6 +257,18 @@ public function validateComposerMetadata(string $url, Response $response): void } } + public function preparePackage(PackageInterface $package): void + { + if ($this->isTufEnabled($package)) { + $options = $package->getTransportOptions(); + $target = $options['tuf']['target']; + // @see ::configurePackageTransportOptions() + $options['max_file_size'] = $this->updater->getLength($target); + $this->io->debug("[TUF] Target '$target' limited to " . $options['max_file_size'] . ' bytes.'); + $package->setTransportOptions($options); + } + } + /** * Validates a downloaded package with TUF. * diff --git a/tests/ApiTest.php b/tests/ApiTest.php index d0650b1..705de26 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -118,10 +118,8 @@ private function mockRepository(Updater $updater = NULL, array $config = []): Tu */ private function setUpdater(TufValidatedComposerRepository $repository, Updater $updater): void { - $reflector = new \ReflectionClass(TufValidatedComposerRepository::class); - $property = $reflector->getProperty('updater'); - $property->setAccessible(true); - $property->setValue($repository, $updater); + $reflector = new \ReflectionProperty(TufValidatedComposerRepository::class, 'updater'); + $reflector->setValue($repository, $updater); } /** @@ -150,20 +148,12 @@ public function configurePackageTransportOptions(PackageInterface $package): voi } }; - $updater = $this->createMock(ComposerCompatibleUpdater::class); - $updater->expects($this->atLeastOnce()) - ->method('getLength') - ->with('drupal/token/1.9.0.0') - ->willReturn(36); - $this->setUpdater($repository, $updater); - $package = new CompletePackage('drupal/token', '1.9.0.0', '1.9.0'); $repository->configurePackageTransportOptions($package); $options = $package->getTransportOptions(); $this->assertArrayHasKey('tuf', $options); $this->assertSame('https://packages.drupal.org/8', $options['tuf']['repository']); $this->assertSame('drupal/token/1.9.0.0', $options['tuf']['target']); - $this->assertSame(36, $options['max_file_size']); } /** diff --git a/tests/ComposerCommandsTest.php b/tests/ComposerCommandsTest.php index 2907e91..4171180 100644 --- a/tests/ComposerCommandsTest.php +++ b/tests/ComposerCommandsTest.php @@ -67,10 +67,13 @@ public function testRequireAndRemove(): void // size, and there should not be a message saying that it was validated. $this->assertStringContainsString("[TUF] Target 'drupal/token~dev.json' limited to " . TufValidatedComposerRepository::MAX_404_BYTES, $debug); $this->assertStringNotContainsStringIgnoringCase("[TUF] Target 'drupal/token~dev.json' validated.", $debug); - // The plugin won't report the maximum download size of package targets; instead, that - // information will be stored in the transport options saved to the lock file. + // The plugin should report the maximum download size of package targets. + $this->assertStringContainsString("[TUF] Target 'drupal/token/1.9.0.0' limited to 114056 bytes.", $debug); + $this->assertStringContainsString("[TUF] Target 'drupal/pathauto/1.12.0.0' limited to 123805 bytes.", $debug); $this->assertStringContainsString("[TUF] Target 'drupal/token/1.9.0.0' validated.", $debug); - // Metapackages should not be validated, because they don't actually install any files. + $this->assertStringContainsString("[TUF] Target 'drupal/pathauto/1.12.0.0' validated.", $debug); + // Metapackages should not be size-limited or validated, because they don't actually install any files. + $this->assertStringNotContainsString("[TUF] Target 'drupal/core/recommended/10.3.0.0' limited to ", $debug); $this->assertStringNotContainsStringIgnoringCase("[TUF] Target 'drupal/core-recommended/10.3.0.0' validated.", $debug); // Even though we are searching delegated roles for multiple targets, we should see the TUF metadata @@ -99,14 +102,12 @@ public function testRequireAndRemove(): void $this->assertIsArray($transportOptions); $this->assertSame('http://localhost:8080', $transportOptions['tuf']['repository']); $this->assertSame('drupal/token/1.9.0.0', $transportOptions['tuf']['target']); - $this->assertNotEmpty($transportOptions['max_file_size']); $transportOptions = $lock->findPackage('drupal/pathauto', '*') ?->getTransportOptions(); $this->assertIsArray($transportOptions); $this->assertSame('http://localhost:8080', $transportOptions['tuf']['repository']); $this->assertSame('drupal/pathauto/1.12.0.0', $transportOptions['tuf']['target']); - $this->assertNotEmpty($transportOptions['max_file_size']); $this->composer(['remove', 'drupal/core-recommended']); $this->assertDirectoryDoesNotExist("$vendorDir/drupal/token");