Skip to content

Commit

Permalink
Merge pull request #298 from vtsykun/feat/public-access
Browse files Browse the repository at this point in the history
Allow public access to some packages via a sub-repository
  • Loading branch information
vtsykun authored Nov 9, 2024
2 parents 707cccd + b6e5fd2 commit 0ea666e
Show file tree
Hide file tree
Showing 17 changed files with 350 additions and 40 deletions.
14 changes: 12 additions & 2 deletions src/Controller/Api/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Packeton\Security\Provider\AuditSessionProvider;
use Packeton\Service\JobPersister;
use Packeton\Service\Scheduler;
use Packeton\Service\SubRepositoryHelper;
use Packeton\Util\PacketonUtils;
use Packeton\Webhook\HookBus;
use Psr\Log\LoggerInterface;
Expand All @@ -43,6 +44,7 @@ public function __construct(
protected ValidatorInterface $validator,
protected IntegrationRegistry $integrations,
protected AuditSessionProvider $auditSessionProvider,
protected SubRepositoryHelper $subRepositoryHelper,
) {
}

Expand Down Expand Up @@ -189,12 +191,14 @@ public function editPackageAction(Request $request, #[Vars] Package $package): R


#[Route('/downloads/{name}', name: 'track_download', requirements: ['name' => '%package_name_regex%'], methods: ['POST'])]
#[Route('/{slug}/downloads/{name}', name: 'track_download_slug', requirements: ['name' => '%package_name_regex%'], methods: ['POST'])]
public function trackDownloadAction(Request $request, $name): Response
{
$allowed = $this->subRepositoryHelper->allowedPackageIds();
$result = $this->getPackageAndVersionId($name, $request->request->get('version_normalized'));

if (!$result) {
return new JsonResponse(['status' => 'error', 'message' => 'Package not found'], 200);
if (!$result || (null !== $allowed && !in_array($result['id'], $allowed, true))) {
return new JsonResponse(['status' => 'error', 'message' => 'Package not found'], 404);
}

$this->downloadManager->addDownloads(['id' => $result['id'], 'vid' => $result['vid'], 'ip' => $request->getClientIp()]);
Expand Down Expand Up @@ -223,6 +227,7 @@ public function getJobAction(string $id): Response
* @inheritDoc
*/
#[Route('/downloads/', name: 'track_download_batch', methods: ['POST'])]
#[Route('/{slug}/downloads/', name: 'track_download_batch_slug', methods: ['POST'])]
public function trackDownloadsAction(Request $request): Response
{
$contents = \json_decode($request->getContent(), true);
Expand All @@ -234,6 +239,7 @@ public function trackDownloadsAction(Request $request): Response
$ip = $request->getClientIp();

$jobs = [];
$allowed = $this->subRepositoryHelper->allowedPackageIds();
foreach ($contents['downloads'] as $package) {
$result = $this->getPackageAndVersionId($package['name'], $package['version']);

Expand All @@ -242,6 +248,10 @@ public function trackDownloadsAction(Request $request): Response
continue;
}

if (null !== $allowed && !in_array($result['id'], $allowed, true)) {
continue;
}

$audit[] = "{$package['name']}: {$package['version']}";
$jobs[] = ['id' => $result['id'], 'vid' => $result['vid'], 'ip' => $ip];
}
Expand Down
14 changes: 5 additions & 9 deletions src/Controller/ProviderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
class ProviderController extends AbstractController
{
use ControllerTrait;
use SubRepoControllerTrait;

public function __construct(
private readonly PackageManager $packageManager,
Expand Down Expand Up @@ -128,16 +129,17 @@ public function packageV2Action(Request $request, string $package): Response
{
$isDev = str_ends_with($package, '~dev');
$packageName = preg_replace('/~dev$/', '', $package);
if (!$this->checkSubrepositoryAccess($packageName)) {
return $this->createNotFound();
}

$response = new JsonResponse([]);
$response->setLastModified($this->providerManager->getLastModify($package));
if ($response->isNotModified($request)) {
return $response;
}

if (!$this->checkSubrepositoryAccess($packageName)) {
return $this->createNotFound();
}

$package = $this->packageManager->getPackageV2Json($this->getUser(), $packageName, $isDev);
if (!$package) {
return $this->createNotFound();
Expand All @@ -154,12 +156,6 @@ protected function createNotFound(?string $msg = null): Response
return new JsonResponse(['status' => 'error', 'message' => $msg ?: 'Not Found'], 404);
}

protected function checkSubrepositoryAccess(string $name): bool
{
$packages = $this->subRepositoryHelper->allowedPackageNames();
return $packages === null || in_array($name, $packages, true);
}

protected function createJsonResponse(array $data): JsonResponse
{
$response = new JsonResponse($data);
Expand Down
14 changes: 14 additions & 0 deletions src/Controller/SubRepoControllerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Packeton\Controller;

trait SubRepoControllerTrait
{
protected function checkSubrepositoryAccess(string $name): bool
{
$packages = $this->subRepositoryHelper->allowedPackageNames();
return $packages === null || in_array($name, $packages, true);
}
}
17 changes: 17 additions & 0 deletions src/Controller/WebController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Doctrine\Persistence\ManagerRegistry;
use Packeton\Entity\Group;
use Packeton\Entity\Package;
use Packeton\Entity\SubRepository;
use Packeton\Entity\Version;
use Packeton\Form\Model\SearchQuery;
use Packeton\Form\Type\SearchQueryType;
Expand Down Expand Up @@ -52,6 +53,22 @@ public function indexAction(Request $request): Response
]);
}

#[Route('/{slug}', name: 'sub_repository_home', methods: ['GET'], priority: -50)]
public function subRepoAction(Request $request, string $slug): Response
{
$repo = $this->registry->getRepository(SubRepository::class)->findOneBy(['slug' => $slug]);
if (!$repo instanceof SubRepository) {
throw $this->createNotFoundException();
}

$isHost = $this->subRepositoryHelper->getByHost($request->getHost());

return $this->render('subrepository/public.html.twig', [
'repo' => $repo,
'repoUrl' => $request->getSchemeAndHttpHost() . ($isHost ? '' : '/'.$slug),
]);
}

/**
* Rendered by views/Web/searchSection.html.twig
*/
Expand Down
28 changes: 23 additions & 5 deletions src/Controller/ZipballController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Packeton\Model\UploadZipballStorage;
use Packeton\Package\RepTypes;
use Packeton\Service\DistManager;
use Packeton\Service\SubRepositoryHelper;
use Packeton\Util\PacketonUtils;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
Expand All @@ -27,11 +28,14 @@
#[Route(defaults: ['_format' => 'json'])]
class ZipballController extends AbstractController
{
use SubRepoControllerTrait;

public function __construct(
protected DistManager $dm,
protected UploadZipballStorage $storage,
protected ManagerRegistry $registry,
protected EventDispatcherInterface $dispatcher,
protected SubRepositoryHelper $subRepositoryHelper,
protected LoggerInterface $logger,
) {
}
Expand Down Expand Up @@ -85,19 +89,33 @@ public function zipballList(Request $request): Response
requirements: ['package' => '%package_name_regex%', 'hash' => '[a-f0-9]{40}(\.?[A-Za-z\.]+?)?'],
methods: ['GET']
)]
#[Route(
'/{slug}/zipball/{package}/{hash}',
name: 'download_dist_package_slug',
requirements: ['package' => '%package_name_regex%', 'hash' => '[a-f0-9]{40}(\.?[A-Za-z\.]+?)?'],
methods: ['GET']
)]
public function zipballAction(#[Vars('name')] Package $package, string $hash): Response
{
if ((false === $this->dm->isEnabled() && false === RepTypes::isBuildInDist($package->getRepoType()))
|| !\preg_match('{[a-f0-9]{40}}i', $hash, $match) || !($reference = $match[0])
|| !\preg_match('{[a-f0-9]{40}}i', $hash, $match)
|| !($reference = $match[0])
|| !$this->checkSubrepositoryAccess($package->getName())
) {
return $this->createNotFound();
}

$isGranted = $this->isGranted('VIEW_ALL_VERSION', $package) || $this->isGranted('ROLE_FULL_CUSTOMER', $package);
foreach ($package->getAllVersionsByReference($reference) as $version) {
$isGranted |= $this->isGranted('ROLE_FULL_CUSTOMER', $version);
$isGranted = $this->subRepositoryHelper->isPublicAccess()
|| $this->isGranted('VIEW_ALL_VERSION', $package)
|| $this->isGranted('ROLE_FULL_CUSTOMER', $package);

if (false === $isGranted) {
foreach ($package->getAllVersionsByReference($reference) as $version) {
$isGranted = $isGranted || $this->isGranted('ROLE_FULL_CUSTOMER', $version);
}
}
if (!$isGranted) {

if (true !== $isGranted) {
return $this->createNotFound();
}

Expand Down
28 changes: 28 additions & 0 deletions src/Entity/SubRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class SubRepository
#[ORM\Column(type: 'json', nullable: true)]
private ?array $packages = null;

#[ORM\Column(name: 'public_access', type: 'boolean', nullable: true)]
private ?bool $publicAccess = null;

#[ORM\Column(name: 'html_markup', type: 'text', nullable: true)]
private ?string $htmlMarkup = null;

/** @internal */
private ?array $cachedIds = null;

Expand Down Expand Up @@ -129,4 +135,26 @@ public function setCachedIds(?array $cachedIds): static
$this->cachedIds = $cachedIds;
return $this;
}

public function isPublicAccess(): ?bool
{
return $this->publicAccess;
}

public function setPublicAccess(?bool $publicAccess): static
{
$this->publicAccess = $publicAccess;
return $this;
}

public function getHtmlMarkup(): ?string
{
return $this->htmlMarkup;
}

public function setHtmlMarkup(?string $htmlMarkup): static
{
$this->htmlMarkup = $htmlMarkup;
return $this;
}
}
8 changes: 6 additions & 2 deletions src/EventListener/PackagistListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Packeton\Event\FormHandlerEvent;
use Packeton\Model\ProviderManager;
use Packeton\Service\DistConfig;
use Packeton\Service\SubRepositoryHelper;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

Expand All @@ -38,7 +39,8 @@ class PackagistListener
public function __construct(
private readonly RequestStack $requestStack,
private readonly ProviderManager $providerManager,
){
private readonly SubRepositoryHelper $subRepositoryHelper,
) {
}

/**
Expand All @@ -56,8 +58,10 @@ public function postLoad(Version $version, PostLoadEventArgs $event)
$dist = $version->getDist();
if (isset($dist['url']) && \str_starts_with($dist['url'], DistConfig::HOSTNAME_PLACEHOLDER)) {
$currentHost = $request->getSchemeAndHttpHost();
$slug = $this->subRepositoryHelper->getCurrentSlug();
$replacement = null !== $slug ? $currentHost . '/' . $slug : $currentHost;

$dist['url'] = \str_replace(DistConfig::HOSTNAME_PLACEHOLDER, $currentHost, $dist['url']);
$dist['url'] = \str_replace(DistConfig::HOSTNAME_PLACEHOLDER, $replacement, $dist['url']);
$version->distNormalized = $dist;
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/EventListener/ProtectHostListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ class ProtectHostListener
'root_package_v2' => 1,
'download_dist_package' => 1,
'track_download' => 1,
'track_download_batch' =>1,
'root_packages_slug' =>1,
'track_download_batch' => 1,
'root_packages_slug' => 1,
'root_providers_slug' => 1,
'root_package_slug' => 1,
'root_package_v2_slug' => 1,
'download_dist_package_slug' => 1,
'track_download_batch_slug' => 1,
'track_download_slug' => 1,
'mirror_root' => 1,
'mirror_metadata_v2' => 1,
'mirror_metadata_v1' => 1,
Expand Down
Loading

0 comments on commit 0ea666e

Please sign in to comment.