From 9521a0f8a6739c01eb0ddfe7d8265ba89fb62965 Mon Sep 17 00:00:00 2001 From: Uladzimir Tsykun Date: Thu, 8 Feb 2024 22:41:12 +0100 Subject: [PATCH] Support setting updates expiration date on user groups level --- src/Entity/Group.php | 21 ++++++++++++ src/Form/Type/CustomerUserType.php | 4 +-- src/Form/Type/GroupType.php | 9 +++++- src/Repository/GroupRepository.php | 22 +++++++++++++ src/Security/Acl/PackagesAclChecker.php | 43 +++++++++++++------------ templates/group/update.html.twig | 13 ++++++++ 6 files changed, 88 insertions(+), 24 deletions(-) diff --git a/src/Entity/Group.php b/src/Entity/Group.php index 337ed34c..7b771765 100644 --- a/src/Entity/Group.php +++ b/src/Entity/Group.php @@ -19,6 +19,9 @@ class Group #[ORM\Column(name: 'name', length: 64, unique: true)] private ?string $name = null; + #[ORM\Column(name: 'expired_updates_at', type: 'datetime', nullable: true)] + private ?\DateTimeInterface $expiredUpdatesAt = null; + #[ORM\Column(name: 'proxies', type: 'simple_array', nullable: true)] private ?array $proxies = null; @@ -141,4 +144,22 @@ public function removeAclPermissions(GroupAclPermission $permission) return $this; } + + /** + * {@inheritdoc} + */ + public function getExpiredUpdatesAt(): ?\DateTimeInterface + { + return $this->expiredUpdatesAt; + } + + /** + * @param \DateTimeInterface $expiredUpdatesAt + * @return $this + */ + public function setExpiredUpdatesAt(?\DateTimeInterface $expiredUpdatesAt) + { + $this->expiredUpdatesAt = $expiredUpdatesAt; + return $this; + } } diff --git a/src/Form/Type/CustomerUserType.php b/src/Form/Type/CustomerUserType.php index d14c64a0..0e14fc67 100644 --- a/src/Form/Type/CustomerUserType.php +++ b/src/Form/Type/CustomerUserType.php @@ -56,8 +56,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->add('expiredUpdatesAt', DateType::class, [ 'required' => false, 'widget' => 'single_text', - 'label' => 'Update expiration', - 'tooltip' => 'A new release updates will be frozen after this date. But the user can uses the versions released before' + 'label' => 'Update expiration date', + 'tooltip' => 'Default release updates expiration date. By default the value from the ACL group will be used if it is set' ]) ->add('fullAccess', CheckboxType::class, [ 'required' => false, diff --git a/src/Form/Type/GroupType.php b/src/Form/Type/GroupType.php index 73cc2701..99bd41b3 100644 --- a/src/Form/Type/GroupType.php +++ b/src/Form/Type/GroupType.php @@ -6,6 +6,7 @@ use Packeton\Mirror\ProxyRepositoryRegistry; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -26,7 +27,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $proxyChoice = \array_combine($proxyChoice, $proxyChoice); $builder - ->add('name', TextType::class, ['label' => 'Name', 'constraints' => [new NotBlank()]]); + ->add('name', TextType::class, ['label' => 'Name', 'constraints' => [new NotBlank()]]) + ->add('expiredUpdatesAt', DateType::class, [ + 'widget' => 'single_text', + 'required' => false, + 'label' => 'Update expiration', + 'tooltip' => 'A new release updates will be frozen after this date. But the user can uses the versions released before.' + ]); if ($proxyChoice) { $builder diff --git a/src/Repository/GroupRepository.php b/src/Repository/GroupRepository.php index 3d1b218e..07a38f13 100644 --- a/src/Repository/GroupRepository.php +++ b/src/Repository/GroupRepository.php @@ -64,6 +64,27 @@ public function getAllowedVersionByPackage(?UserInterface $user, Package $packag return $result; } + public function getExpirationDateForUser(PacketonUserInterface $user): array + { + $qb = $this->getEntityManager()->createQueryBuilder(); + + $result = []; + $expiredData = $qb->select('MAX(g.expiredUpdatesAt) as expired', 'IDENTITY(perm.package) as package') + ->from(Group::class, 'g') + ->innerJoin('g.aclPermissions', 'perm') + ->andWhere('g.expiredUpdatesAt IS NOT NULL') + ->andWhere('g.id IN (:ids)') + ->groupBy('package') + ->setParameter('ids', $user->getAclGroups() ?: [-1]) + ->getQuery()->getArrayResult(); + + foreach ($expiredData as $item) { + $result[(int)$item['package']] = new \DateTimeImmutable($item['expired'], new \DateTimeZone('UTC')); + } + + return $result; + } + /** * @param UserInterface $user * @param bool $hydration flags @@ -161,6 +182,7 @@ public function getApiData(Group $group) 'id' => $group->getId(), 'name' => $group->getName(), 'proxies' => $group->getProxies(), + 'expiredUpdatesAt' => $group->getExpiredUpdatesAt(), ] + $this->getGroupsData($group); } diff --git a/src/Security/Acl/PackagesAclChecker.php b/src/Security/Acl/PackagesAclChecker.php index e886ab81..baeb7991 100644 --- a/src/Security/Acl/PackagesAclChecker.php +++ b/src/Security/Acl/PackagesAclChecker.php @@ -9,30 +9,17 @@ use Packeton\Entity\Package; use Packeton\Entity\Version; use Packeton\Model\PacketonUserInterface as PUI; +use Packeton\Repository\GroupRepository; class PackagesAclChecker { - /** - * @var ManagerRegistry - */ - private $registry; - /** - * @var VersionParser - */ - private $parser; + private VersionParser $parser; + private array $versionCache = []; + private array $expiredCache = []; - /** - * @var array - */ - private $versionCache = []; - - /** - * @param ManagerRegistry $registry - */ - public function __construct(ManagerRegistry $registry) + public function __construct(private ManagerRegistry $registry) { - $this->registry = $registry; $this->parser = new VersionParser(); } @@ -55,6 +42,9 @@ public function isGrantedAccessForAllVersions(PUI $user, Package $package) if ($package->isFullVisibility()) { return true; } + if ($this->getExpiredDate($user, $package)) { + return false; + } $versionConstraints = $this->getVersions($user, $package); foreach ($versionConstraints as $constraint) { @@ -76,7 +66,7 @@ public function isGrantedAccessForAllVersions(PUI $user, Package $package) */ public function isGrantedAccessForVersion(PUI $user, Version $version) { - if ($user->getExpiredUpdatesAt() && $user->getExpiredUpdatesAt() < $version->getReleasedAt()) { + if (($date = $this->getExpiredDate($user, $version->getPackage())) && $date < $version->getReleasedAt()) { return false; } @@ -102,8 +92,19 @@ private function getVersions(PUI $user, Package $package) return $this->versionCache[$hash]; } - $version = $this->registry->getRepository(Group::class) - ->getAllowedVersionByPackage($user, $package); + $version = $this->getGroupRepo()->getAllowedVersionByPackage($user, $package); return $this->versionCache[$hash] = $version; } + + public function getExpiredDate(PUI $user, Package $package): ?\DateTimeInterface + { + $expiredData = $this->expiredCache[$user->getUserIdentifier()] ??= $this->getGroupRepo()->getExpirationDateForUser($user); + + return $expiredData[$package->getId()] ?? $user->getExpiredUpdatesAt(); + } + + private function getGroupRepo(): GroupRepository + { + return $this->registry->getRepository(Group::class); + } } diff --git a/templates/group/update.html.twig b/templates/group/update.html.twig index 86ea39cd..09f9d67c 100644 --- a/templates/group/update.html.twig +++ b/templates/group/update.html.twig @@ -13,6 +13,7 @@ {{ form_start(form, { attr: { class: 'col-md-6' } }) }} {{ form_errors(form) }} {{ form_row(form.name) }} + {{ form_row(form.expiredUpdatesAt) }} {% if form.proxies is defined %} {{ form_row(form.proxies) }} {% endif %} @@ -64,3 +65,15 @@ }); {% endblock %} + +{% block scripts %} + + + +{% endblock %}