Skip to content

Commit

Permalink
Add detection for repo id changes on github/gitlab, preventing repoja…
Browse files Browse the repository at this point in the history
…cked repos from updating further
  • Loading branch information
Seldaek committed Jan 10, 2024
1 parent a1317cc commit 9b8cfaa
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 25 deletions.
50 changes: 25 additions & 25 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions src/Controller/PackageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,26 @@ public function markSafeAction(Request $req): RedirectResponse
return $this->redirectToRoute('view_spam');
}

#[IsGranted('ROLE_ADMIN')]
#[Route(path: '/package/{name}/unfreeze', name: 'unfreeze_package', requirements: ['name' => '[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?'], defaults: ['_format' => 'html'], methods: ['POST'])]
public function unfreezePackageAction(Request $req, string $name, CsrfTokenManagerInterface $csrfTokenManager): RedirectResponse
{
if (!$this->isCsrfTokenValid('unfreeze', (string) $req->request->get('token'))) {
throw new BadRequestHttpException('Invalid CSRF token');
}

$package = $this->getPackageByName($req, $name);
if ($package instanceof Response) {
return $package;
}

$package->unfreeze();
$this->getEM()->persist($package);
$this->getEM()->flush();

return $this->redirectToRoute('view_package', ['name' => $package->getName()]);
}

#[Route(path: '/packages/{name}.{_format}', name: 'view_package', requirements: ['name' => '[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?', '_format' => '(json)'], defaults: ['_format' => 'html'], methods: ['GET'])]
public function viewPackageAction(Request $req, string $name, CsrfTokenManagerInterface $csrfTokenManager, #[CurrentUser] ?User $user = null): Response
{
Expand Down Expand Up @@ -694,6 +714,9 @@ public function viewPackageAction(Request $req, string $name, CsrfTokenManagerIn
if ($this->isGranted('ROLE_ANTISPAM')) {
$data['markSafeCsrfToken'] = $csrfTokenManager->getToken('mark_safe');
}
if ($this->isGranted('ROLE_ADMIN')) {
$data['unfreezeCsrfToken'] = $csrfTokenManager->getToken('unfreeze');
}

return $this->render('package/view_package.html.twig', $data);
}
Expand Down Expand Up @@ -965,6 +988,8 @@ public function editAction(Request $req, #[MapEntity] Package $package, #[Curren
if ($form->isSubmitted() && $form->isValid()) {
// Force updating of packages once the package is viewed after the redirect.
$package->setCrawledAt(null);
// Reset remoteId as if the URL changes we expect possibly a different id and that's ok
$package->setRemoteId(null);

$em = $this->getEM();
$em->persist($package);
Expand Down
41 changes: 41 additions & 0 deletions src/Entity/Package.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
use Composer\Util\HttpDownloader;
use DateTimeInterface;

enum PackageFreezeReason: string
{
case Spam = 'spam';
case RemoteIdMismatch = 'remote_id';
}

/**
* @author Jordi Boggiano <[email protected]>
* @phpstan-import-type VersionArray from Version
Expand Down Expand Up @@ -163,6 +169,12 @@ class Package
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private string|null $suspect = null;

/**
* If set, the content is the reason for being frozen
*/
#[ORM\Column(nullable: true)]
private PackageFreezeReason|null $frozen = null;

/**
* @internal
* @var true|null|\Composer\Repository\Vcs\VcsDriverInterface
Expand Down Expand Up @@ -720,6 +732,35 @@ public function getSuspect(): ?string
return $this->suspect;
}

public function freeze(PackageFreezeReason $reason): void
{
$this->frozen = $reason;
$this->setCrawledAt($dt = new \DateTimeImmutable('2100-01-01 00:00:00'));
$this->setDumpedAt($dt);
$this->setDumpedAtV2($dt);
}

public function unfreeze(): void
{
if ($this->frozen === PackageFreezeReason::RemoteIdMismatch) {
$this->setRemoteId(null);
}
$this->frozen = null;
$this->setCrawledAt(null);
$this->setDumpedAt(null);
$this->setDumpedAtV2(null);
}

public function isFrozen(): bool
{
return !is_null($this->frozen);
}

public function getFreezeReason(): ?PackageFreezeReason
{
return $this->frozen;
}

public function isAbandoned(): bool
{
return $this->abandoned;
Expand Down
43 changes: 43 additions & 0 deletions src/Package/Updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace App\Package;

use App\Entity\Dependent;
use App\Entity\PackageFreezeReason;
use cebe\markdown\GithubMarkdown;
use Composer\Package\AliasPackage;
use Composer\Pcre\Preg;
Expand All @@ -37,6 +38,10 @@
use Doctrine\DBAL\Connection;
use App\Service\VersionCache;
use Composer\Package\CompletePackageInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Webmozart\Assert\Assert;

/**
Expand Down Expand Up @@ -77,6 +82,9 @@ public function __construct(
private ManagerRegistry $doctrine,
private ProviderManager $providerManager,
private VersionIdCache $versionIdCache,
private MailerInterface $mailer,
private string $mailFromEmail,
private UrlGeneratorInterface $urlGenerator,
) {
ErrorHandler::register();
}
Expand All @@ -103,6 +111,41 @@ public function update(IOInterface $io, Config $config, Package $package, VcsRep
throw new \RuntimeException('Driver could not be established for package '.$package->getName().' ('.$package->getRepository().')');
}

$remoteId = null;
if ($driver instanceof GitHubDriver) {
$repoData = $driver->getRepoData();
if (isset($repoData['repository']['id'])) {
$remoteId = 'github.com/'.$repoData['repository']['id'];
}
} elseif ($driver instanceof GitLabDriver) {
$repoData = $driver->getRepoData();
if (isset($repoData['id'])) {
$remoteId = 'gitlab.com/'.$repoData['id'];
}
}

if ($remoteId !== null) {
if (!$package->getRemoteId()) {
$package->setRemoteId($remoteId);
}
if ($package->getRemoteId() !== $remoteId) {
$package->freeze(PackageFreezeReason::RemoteIdMismatch);
$em->flush();
$io->writeError('<error>Skipping update as the source repository has a remote id mismatch. Expected '.$package->getRemoteId().' but got ' . $remoteId.'.</error>');

$message = (new Email())
->subject($package->getName().' frozen due to remote id mismatch')
->from(new Address($this->mailFromEmail))
->to($this->mailFromEmail)
->text('Check out '.$this->urlGenerator->generate('view_package', ['name' => $package->getName()], UrlGeneratorInterface::ABSOLUTE_URL).' was not repo-jacked.')
;
$message->getHeaders()->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply');
$this->mailer->send($message);

return $package;
}
}

$rootIdentifier = $driver->getRootIdentifier();

// always update the master branch / root identifier, as in case a package gets archived
Expand Down
9 changes: 9 additions & 0 deletions templates/package/view_package.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,16 @@
<input class="btn btn-danger" type="submit" value="Mark Not Spam" />
</form>
{% endif %}
{% if is_granted('ROLE_ADMIN') and package.isFrozen() %}
<form class="action" action="{{ path("unfreeze_package", {name: package.name}) }}" method="POST">
<input type="hidden" name="token" value="{{ unfreezeCsrfToken }}" />
<input class="btn btn-danger" type="submit" value="Unfreeze" />
</form>
{% endif %}
</div>
{% if package.isFrozen() %}
<p class="alert alert-danger">{{ ('freezing_reasons.' ~ package.getFreezeReason().value)|trans }}</p>
{% endif %}
{% if lastJobWarning is defined and lastJobWarning is not empty %}
<p class="alert alert-danger">{{ lastJobWarning|raw }} <br /> <a href="#" data-msg="{{ lastJobMsg }}" data-details="{{ lastJobDetails }}" class="view-log">View Last Update Log</a></p>
{% endif %}
Expand Down
4 changes: 4 additions & 0 deletions translations/messages.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,7 @@ Order: 'Order'
Username: 'Username'
asc: 'asc'
desc: 'desc'

freezing_reasons:
spam: This package was marked as spam and has been frozen as a result.
remote_id: This packages's canonical repository id has changed and the package has been frozen as a result.

0 comments on commit 9b8cfaa

Please sign in to comment.