Skip to content

Commit

Permalink
Prevent Composer from asking TUF for information about packages it do…
Browse files Browse the repository at this point in the history
…esn't intend to download (#119)
  • Loading branch information
phenaproxima authored Jul 12, 2024
1 parent e5eab1f commit 17de261
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 57 deletions.
65 changes: 33 additions & 32 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand All @@ -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;
}

Expand Down
25 changes: 17 additions & 8 deletions src/TufValidatedComposerRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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.
*
Expand Down
14 changes: 2 additions & 12 deletions tests/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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']);
}

/**
Expand Down
11 changes: 6 additions & 5 deletions tests/ComposerCommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 17de261

Please sign in to comment.