Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ parameters:
PackageUpdateJob: 'array{id: int, update_equal_refs: bool, delete_before: bool, force_dump: bool, source: string}'
GitHubUserMigrateJob: 'array{id: int, old_scope: string, new_scope: string}'
SecurityAdvisoryJob: 'array{source: string}'
FilterListJob: 'array{list: string}'
FilterListJob: 'array{list: string, source: string}'

JobResult: 'array{status: \App\Entity\Job::STATUS_*, message: string, vendor?: string, details?: string, exceptionMsg?: string, exceptionClass?: class-string<\Throwable>, results?: array{hooks_setup: int, hooks_failed: array<int, array{package: string, reason: mixed}>, hooks_ok_unchanged: int}}'
ErroredResult: 'array{status: \App\Entity\Job::STATUS_ERRORED, message: string, exception: \Throwable}'
Expand Down
3 changes: 3 additions & 0 deletions src/Audit/Display/AuditLogDisplayFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use App\Entity\AuditRecord;
use App\Entity\User;
use App\FilterList\FilterLists;
use App\FilterList\FilterSources;
use Symfony\Bundle\SecurityBundle\Security;

class AuditLogDisplayFactory
Expand Down Expand Up @@ -222,6 +223,7 @@ public function buildSingle(AuditRecord $record): AuditLogDisplayInterface
$record->attributes['entry']['package_name'],
$record->attributes['entry']['version'],
FilterLists::from($record->attributes['entry']['list']),
FilterSources::from($record->attributes['entry']['source']),
$record->attributes['entry']['reason'] ?? $record->attributes['entry']['category'],
$this->buildActor($record->attributes['actor'] ?? null),
$record->ip
Expand All @@ -231,6 +233,7 @@ public function buildSingle(AuditRecord $record): AuditLogDisplayInterface
$record->attributes['entry']['package_name'],
$record->attributes['entry']['version'],
FilterLists::from($record->attributes['entry']['list']),
FilterSources::from($record->attributes['entry']['source']),
$record->attributes['entry']['reason'] ?? $record->attributes['entry']['category'],
$this->buildActor($record->attributes['actor'] ?? null),
$record->ip
Expand Down
2 changes: 2 additions & 0 deletions src/Audit/Display/FilterListEntryAddedDisplay.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use App\Audit\AuditRecordType;
use App\FilterList\FilterLists;
use App\FilterList\FilterSources;

readonly class FilterListEntryAddedDisplay extends AbstractAuditLogDisplay
{
Expand All @@ -22,6 +23,7 @@ public function __construct(
public string $packageName,
public string $version,
public FilterLists $list,
public FilterSources $source,
public string $reason,
ActorDisplay $actor,
?string $ip,
Expand Down
2 changes: 2 additions & 0 deletions src/Audit/Display/FilterListEntryDeletedDisplay.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use App\Audit\AuditRecordType;
use App\FilterList\FilterLists;
use App\FilterList\FilterSources;

readonly class FilterListEntryDeletedDisplay extends AbstractAuditLogDisplay
{
Expand All @@ -22,6 +23,7 @@ public function __construct(
public string $packageName,
public string $version,
public FilterLists $list,
public FilterSources $source,
public string $reason,
ActorDisplay $actor,
?string $ip,
Expand Down
14 changes: 12 additions & 2 deletions src/Command/UpdateFilterListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace App\Command;

use App\FilterList\FilterLists;
use App\FilterList\FilterSources;
use App\Service\Locker;
use App\Service\Scheduler;
use Symfony\Component\Console\Command\Command;
Expand All @@ -35,8 +36,9 @@ protected function configure(): void
->setName('packagist:filter-list')
->setDefinition([
new InputArgument('list', InputArgument::REQUIRED, 'The name of the filter list', null, FilterLists::cases()),
new InputArgument('source', InputArgument::REQUIRED, 'The name of the filter source', null, FilterSources::cases()),
])
->setDescription('Updates all entries for a single filter list')
->setDescription('Updates all entries for a single filter list source')
;
}

Expand All @@ -50,12 +52,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return self::INVALID;
}

try {
$source = FilterSources::from($input->getArgument('source'));
} catch (\ValueError) {
$output->writeln('source must be one of '.implode(', ', array_map(fn (FilterSources $source) => $source->value, FilterSources::cases())));

return self::INVALID;
}

$lockAcquired = $this->locker->lockFilterList($list->value);
if (!$lockAcquired) {
return 0;
}

$this->scheduler->scheduleFilterList($list, 0);
$this->scheduler->scheduleFilterList($list, $source, 0);
sleep(2); // sleep to prevent running the same command on multiple machines at around the same time via cron

$this->locker->unlockFilterList($list->value);
Expand Down
11 changes: 7 additions & 4 deletions src/Controller/PackageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use App\Entity\Version;
use App\Event\PackageAbandonedEvent;
use App\Event\PackageUnabandonedEvent;
use App\FilterList\FilterLists;
use App\Form\Model\MaintainerRequest;
use App\Form\Model\TransferPackageRequest;
use App\Form\Type\AbandonedType;
Expand Down Expand Up @@ -74,6 +75,7 @@
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Requirement\EnumRequirement;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
Expand Down Expand Up @@ -681,7 +683,7 @@ public function viewPackageAction(Request $req, string $name, CsrfTokenManagerIn
foreach ($versions as $version) {
if ($version->getNormalizedVersion() === $normalizedVersion) {
$data['hasVersionsFlaggedAsMalware'][$version->getId()] = true;
$data['listsFlaggingVersionsAsMalware'][$packageVersionFlaggedAsMalware->getList()->value] = $packageVersionFlaggedAsMalware->getList();
$data['listsFlaggingVersionsAsMalware'][$packageVersionFlaggedAsMalware->getSource()->value] = $packageVersionFlaggedAsMalware->getSource();
}
}
}
Expand Down Expand Up @@ -1656,12 +1658,12 @@ public function securityAdvisoryAction(Request $request, string $id): Response
return $this->render('package/security_advisory.html.twig', ['securityAdvisories' => $securityAdvisories, 'id' => $id]);
}

#[Route(path: '/packages/{name}/filter-lists/', name: 'view_package_filter_lists', requirements: ['name' => Package::PACKAGE_NAME_OR_EXT_REGEX])]
public function filterListsAction(Request $request, string $name): Response
#[Route(path: '/packages/{name}/filter-lists/{list}/', name: 'view_package_filter_lists', requirements: ['name' => Package::PACKAGE_NAME_OR_EXT_REGEX, 'list' => new EnumRequirement(FilterLists::class)])]
public function filterListsAction(Request $request, string $name, FilterLists $list): Response
{
/** @var FilterListEntryRepository $repo */
$repo = $this->getEM()->getRepository(FilterListEntry::class);
$entries = $repo->getPackageEntries($name);
$entries = $repo->getPackageEntries($name, $list);

$data = [];
$data['name'] = $name;
Expand Down Expand Up @@ -1693,6 +1695,7 @@ public function filterListsAction(Request $request, string $name): Response

$data['entries'] = $entries;
$data['count'] = \count($entries);
$data['list'] = $list;

return $this->render('package/filter_list_entries.html.twig', $data);
}
Expand Down
16 changes: 13 additions & 3 deletions src/Entity/FilterListEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace App\Entity;

use App\FilterList\FilterLists;
use App\FilterList\FilterSources;
use App\FilterList\RemoteFilterListEntry;
use App\Service\IdGenerator;
use Doctrine\ORM\Mapping as ORM;
Expand Down Expand Up @@ -48,8 +49,11 @@ class FilterListEntry
#[ORM\Column]
private \DateTimeImmutable $updatedAt;

#[ORM\Column(nullable: true)]
private ?string $publicId;
#[ORM\Column]
private string $publicId;

#[ORM\Column]
private FilterSources $source;

public function __construct(RemoteFilterListEntry $remote)
{
Expand All @@ -59,6 +63,7 @@ public function __construct(RemoteFilterListEntry $remote)
$this->link = $remote->link;
$this->list = $remote->list;
$this->reason = $remote->reason;
$this->source = $remote->source;

$this->createdAt = $this->updatedAt = new \DateTimeImmutable();
}
Expand Down Expand Up @@ -93,7 +98,12 @@ public function getPublicId(): ?string
return $this->publicId;
}

public function assignPublicId(): void
public function getSource(): FilterSources
{
return $this->source;
}

private function assignPublicId(): void
{
$this->publicId = IdGenerator::generateFilterListEntry();
}
Expand Down
16 changes: 11 additions & 5 deletions src/Entity/FilterListEntryRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace App\Entity;

use App\FilterList\FilterLists;
use App\FilterList\FilterSources;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\Persistence\ManagerRegistry;
Expand All @@ -31,11 +32,13 @@ public function __construct(
/**
* @return list<FilterListEntry>
*/
public function getEntriesInList(FilterLists $list): array
public function getEntriesInList(FilterLists $list, FilterSources $source): array
{
return $this->createQueryBuilder('fl')
->where('fl.list = :list')
->andWhere('fl.source = :source')
->setParameter('list', $list)
->setParameter('source', $source)
->getQuery()
->getResult();
}
Expand All @@ -47,29 +50,31 @@ public function getPackageVersionsFlaggedAsMalwareForPackage(Package $package):
{
return $this->createQueryBuilder('fl')
->where('fl.packageName = :packageName')
->andWhere('fl.list IN (:lists)')
->andWhere('fl.list = :malware')
->setParameter('packageName', $package->getName())
->setParameter('lists', FilterLists::malwareListsValues(), ArrayParameterType::STRING)
->setParameter('malware', FilterLists::MALWARE)
->getQuery()
->getResult();
}

/**
* @return list<FilterListEntry>
*/
public function getPackageEntries(string $packageName): array
public function getPackageEntries(string $packageName, FilterLists $list): array
{
return $this->createQueryBuilder('fl')
->where('fl.packageName = :packageName')
->andWhere('fl.list = :list')
->setParameter('packageName', $packageName)
->setParameter('list', $list)
->getQuery()
->getResult();
}

/**
* @param array<string> $packageNames
*
* @return array<string, non-empty-list<array{version: string, list: string, reason: string|null, publicId: string|null}>>
* @return array<string, non-empty-list<array{version: string, list: string, reason: string|null, publicId: string|null, source: string}>>
*/
public function getAllPackageEntriesMap(array $packageNames): array
{
Expand All @@ -86,6 +91,7 @@ public function getAllPackageEntriesMap(array $packageNames): array
'list' => $entry->getList()->value,
'reason' => $entry->getReason(),
'publicId' => $entry->getPublicId(),
'source' => $entry->getSource()->value,
];
}

Expand Down
3 changes: 3 additions & 0 deletions src/FilterList/Dump/DumpableFilterList.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@

namespace App\FilterList\Dump;

use App\FilterList\FilterSources;

final readonly class DumpableFilterList
{
public function __construct(
public string $constraint,
public string $url,
public ?string $reason,
public ?string $id,
public string $source,
) {
}
}
3 changes: 2 additions & 1 deletion src/FilterList/Dump/FilterListDumperProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ public function getEntriesForDump(array $packageNames): array
foreach ($entries as $entry) {
$groupedEntries[$packageName][$entry['list']][] = new DumpableFilterList(
$entry['version'],
$this->urlGenerator->generate('view_package_filter_lists', ['name' => $packageName], UrlGeneratorInterface::ABSOLUTE_URL),
$this->urlGenerator->generate('view_package_filter_lists', ['name' => $packageName, 'list' => $entry['list']], UrlGeneratorInterface::ABSOLUTE_URL),
$entry['reason'],
$entry['publicId'],
$entry['source'],
);
}
}
Expand Down
9 changes: 1 addition & 8 deletions src/FilterList/FilterListResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,12 @@ class FilterListResolver
* @param array<FilterListEntry> $existingEntries
* @param array<RemoteFilterListEntry> $remoteEntries
*
* @return array{list<FilterListEntry>, list<FilterListEntry>, bool}
* @return array{list<FilterListEntry>, list<FilterListEntry>}
*/
public function resolve(array $existingEntries, array $remoteEntries): array
{
$modifiedExisting = false;
$existingMap = [];
foreach ($existingEntries as $existing) {
if (!$existing->getPublicId()) {
$existing->assignPublicId();
$modifiedExisting = true;
}

$existingMap[$existing->getPackageName()][$existing->getVersion()] = $existing;
}

Expand All @@ -58,7 +52,6 @@ public function resolve(array $existingEntries, array $remoteEntries): array
return [
$new,
$unmatched,
$modifiedExisting,
];
}
}
47 changes: 1 addition & 46 deletions src/FilterList/FilterLists.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,5 @@

enum FilterLists: string
{
case AIKIDO_MALWARE = 'aikido-malware';

public function logo(): string
{
return match ($this) {
self::AIKIDO_MALWARE => 'img/aikido-dark.svg',
};
}

public function displayName(): string
{
return match ($this) {
self::AIKIDO_MALWARE => 'Aikido',
};
}

public function url(): string
{
return match ($this) {
self::AIKIDO_MALWARE => 'https://aikido.dev/',
};
}

/**
* @return list<FilterLists>
*/
public static function defaultLists(): array
{
return [self::AIKIDO_MALWARE];
}

/**
* @return list<FilterLists>
*/
public static function malwareLists(): array
{
return [self::AIKIDO_MALWARE];
}

/**
* @return list<string>
*/
public static function malwareListsValues(): array
{
return array_map(fn (FilterLists $list) => $list->value, self::malwareLists());
}
case MALWARE = 'malware';
}
Loading