This commit is contained in:
Julien Fastré 2022-04-13 23:17:16 +02:00
parent 2a53fb9341
commit 35c7d55b8c
5 changed files with 153 additions and 149 deletions

View File

@ -32,16 +32,17 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
/** /**
* @Route("/{_locale}/notification") * @Route("/{_locale}/notification")
*/ */
class NotificationController extends AbstractController class NotificationController extends AbstractController
{ {
private EntityManagerInterface $em;
private LoggerInterface $chillLogger; private LoggerInterface $chillLogger;
private EntityManagerInterface $em;
private LoggerInterface $logger; private LoggerInterface $logger;
private NotificationHandlerManager $notificationHandlerManager; private NotificationHandlerManager $notificationHandlerManager;
@ -74,49 +75,6 @@ class NotificationController extends AbstractController
$this->translator = $translator; $this->translator = $translator;
} }
/**
* @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key")
*/
public function getAccessByAccessKey(Notification $notification, Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
if (!$this->security->getUser() instanceof User) {
throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification');
}
foreach (['accessKey', 'email'] as $param) {
if (!$request->query->has($param)) {
throw new BadRequestHttpException("Missing $param parameter");
}
}
if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) {
throw new AccessDeniedHttpException('access key is invalid');
}
if (!in_array($request->query->get('email'), $notification->getAddressesEmails())) {
return (new Response('The email address is no more associated with this notification'))
->setStatusCode(Response::HTTP_FORBIDDEN);
}
$notification->addAddressee($this->security->getUser());
$this->getDoctrine()->getManager()->flush();
$logMsg = '[Notification] a user is granted access to notification trough an access key';
$context = [
'notificationId' => $notification->getId(),
'email' => $request->query->get('email'),
'user' => $this->security->getUser()->getId(),
];
$this->logger->info($logMsg, $context);
$this->chillLogger->info($logMsg, $context);
return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]);
}
/** /**
* @Route("/create", name="chill_main_notification_create") * @Route("/create", name="chill_main_notification_create")
*/ */
@ -202,6 +160,49 @@ class NotificationController extends AbstractController
]); ]);
} }
/**
* @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key")
*/
public function getAccessByAccessKey(Notification $notification, Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
if (!$this->security->getUser() instanceof User) {
throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification');
}
foreach (['accessKey', 'email'] as $param) {
if (!$request->query->has($param)) {
throw new BadRequestHttpException("Missing {$param} parameter");
}
}
if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) {
throw new AccessDeniedHttpException('access key is invalid');
}
if (!in_array($request->query->get('email'), $notification->getAddressesEmails(), true)) {
return (new Response('The email address is no more associated with this notification'))
->setStatusCode(Response::HTTP_FORBIDDEN);
}
$notification->addAddressee($this->security->getUser());
$this->getDoctrine()->getManager()->flush();
$logMsg = '[Notification] a user is granted access to notification trough an access key';
$context = [
'notificationId' => $notification->getId(),
'email' => $request->query->get('email'),
'user' => $this->security->getUser()->getId(),
];
$this->logger->info($logMsg, $context);
$this->chillLogger->info($logMsg, $context);
return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]);
}
/** /**
* @Route("/inbox", name="chill_main_notification_my") * @Route("/inbox", name="chill_main_notification_my")
*/ */

View File

@ -19,6 +19,8 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
use function in_array;
/** /**
* @ORM\Entity * @ORM\Entity
@ -32,6 +34,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
*/ */
class Notification implements TrackUpdateInterface class Notification implements TrackUpdateInterface
{ {
/**
* @ORM\Column(type="text", nullable=false)
*/
private string $accessKey;
private array $addedAddresses = []; private array $addedAddresses = [];
/** /**
@ -40,6 +47,21 @@ class Notification implements TrackUpdateInterface
*/ */
private Collection $addressees; private Collection $addressees;
/**
* a list of destinee which will receive notifications.
*
* @var array|string[]
* @ORM\Column(type="json")
*/
private array $addressesEmails = [];
/**
* a list of emails adresses which were added to the notification.
*
* @var array|string[]
*/
private array $addressesEmailsAdded = [];
private ?ArrayCollection $addressesOnLoad = null; private ?ArrayCollection $addressesOnLoad = null;
/** /**
@ -105,25 +127,6 @@ class Notification implements TrackUpdateInterface
*/ */
private ?User $updatedBy; private ?User $updatedBy;
/**
* a list of destinee which will receive notifications
* @var array|string[]
* @ORM\Column(type="json")
*/
private array $addressesEmails = [];
/**
* a list of emails adresses which were added to the notification
*
* @var array|string[]
*/
private array $addressesEmailsAdded = [];
/**
* @ORM\Column(type="text", nullable=false)
*/
private string $accessKey;
public function __construct() public function __construct()
{ {
$this->addressees = new ArrayCollection(); $this->addressees = new ArrayCollection();
@ -133,46 +136,6 @@ class Notification implements TrackUpdateInterface
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24));
} }
/**
* @return array|string[]
*/
public function getAddressesEmails(): array
{
return $this->addressesEmails;
}
/**
* @return array|string[]
*/
public function getAddressesEmailsAdded(): array
{
return $this->addressesEmailsAdded;
}
/**
* @return string
*/
public function getAccessKey(): string
{
return $this->accessKey;
}
public function addAddressesEmail(string $email)
{
if (!in_array($email, $this->addressesEmails, true)) {
$this->addressesEmails[] = $email;
$this->addressesEmailsAdded[] = $email;
}
}
public function removeAddressesEmail(string $email)
{
if (in_array($email, $this->addressesEmails, true)) {
$this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email);
$this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email);
}
}
public function addAddressee(User $addressee): self public function addAddressee(User $addressee): self
{ {
if (!$this->addressees->contains($addressee)) { if (!$this->addressees->contains($addressee)) {
@ -183,12 +146,12 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
/** public function addAddressesEmail(string $email)
* @return array
*/
public function getAddedAddresses(): array
{ {
return $this->addedAddresses; if (!in_array($email, $this->addressesEmails, true)) {
$this->addressesEmails[] = $email;
$this->addressesEmailsAdded[] = $email;
}
} }
public function addComment(NotificationComment $comment): self public function addComment(NotificationComment $comment): self
@ -210,6 +173,30 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
/**
* @Assert\Callback
*
* @param array $payload
*/
public function assertCountAddresses(ExecutionContextInterface $context, $payload): void
{
if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) {
$context->buildViolation('notification.At least one addressee')
->atPath('addressees')
->addViolation();
}
}
public function getAccessKey(): string
{
return $this->accessKey;
}
public function getAddedAddresses(): array
{
return $this->addedAddresses;
}
/** /**
* @return Collection|User[] * @return Collection|User[]
*/ */
@ -223,6 +210,22 @@ class Notification implements TrackUpdateInterface
return $this->addressees; return $this->addressees;
} }
/**
* @return array|string[]
*/
public function getAddressesEmails(): array
{
return $this->addressesEmails;
}
/**
* @return array|string[]
*/
public function getAddressesEmailsAdded(): array
{
return $this->addressesEmailsAdded;
}
public function getComments(): Collection public function getComments(): Collection
{ {
return $this->comments; return $this->comments;
@ -339,6 +342,14 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
public function removeAddressesEmail(string $email)
{
if (in_array($email, $this->addressesEmails, true)) {
$this->addressesEmails = array_filter($this->addressesEmails, static fn ($e) => $e !== $email);
$this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, static fn ($e) => $e !== $email);
}
}
public function removeComment(NotificationComment $comment): self public function removeComment(NotificationComment $comment): self
{ {
$this->comments->removeElement($comment); $this->comments->removeElement($comment);
@ -408,19 +419,4 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
/**
* @Assert\Callback()
* @param ExecutionContextInterface $context
* @param array $payload
* @return void
*/
public function assertCountAddresses(ExecutionContextInterface $context, $payload): void
{
if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) {
$context->buildViolation('notification.At least one addressee')
->atPath('addressees')
->addViolation();
}
}
} }

View File

@ -40,7 +40,7 @@ class NotificationType extends AbstractType
->add('message', ChillTextareaType::class, [ ->add('message', ChillTextareaType::class, [
'required' => false, 'required' => false,
]) ])
->add('addressesEmails', ChillCollectionType::class, [ ->add('addressesEmails', ChillCollectionType::class, [
'label' => 'notification.dest by email', 'label' => 'notification.dest by email',
'help' => 'notification.dest by email help', 'help' => 'notification.dest by email help',
'by_reference' => false, 'by_reference' => false,

View File

@ -88,6 +88,24 @@ final class NotificationTest extends KernelTestCase
$this->assertNotContains($user1, $notification->getUnreadBy()->toArray()); $this->assertNotContains($user1, $notification->getUnreadBy()->toArray());
} }
public function testAddressesEmail(): void
{
$notification = new Notification();
$notification->addAddressesEmail('test');
$notification->addAddressesEmail('other');
$this->assertContains('test', $notification->getAddressesEmails());
$this->assertContains('other', $notification->getAddressesEmails());
$this->assertContains('test', $notification->getAddressesEmailsAdded());
$this->assertContains('other', $notification->getAddressesEmailsAdded());
$notification->removeAddressesEmail('other');
$this->assertNotContains('other', $notification->getAddressesEmails());
$this->assertNotContains('other', $notification->getAddressesEmailsAdded());
}
/** /**
* @dataProvider generateNotificationData * @dataProvider generateNotificationData
*/ */
@ -122,22 +140,4 @@ final class NotificationTest extends KernelTestCase
$this->assertContains($addresseeId, $unreadIds); $this->assertContains($addresseeId, $unreadIds);
} }
} }
public function testAddressesEmail(): void
{
$notification = new Notification();
$notification->addAddressesEmail('test');
$notification->addAddressesEmail('other');
$this->assertContains('test', $notification->getAddressesEmails());
$this->assertContains('other', $notification->getAddressesEmails());
$this->assertContains('test', $notification->getAddressesEmailsAdded());
$this->assertContains('other', $notification->getAddressesEmailsAdded());
$notification->removeAddressesEmail('other');
$this->assertNotContains('other', $notification->getAddressesEmails());
$this->assertNotContains('other', $notification->getAddressesEmailsAdded());
}
} }

View File

@ -1,5 +1,12 @@
<?php <?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1); declare(strict_types=1);
namespace Chill\Migrations\Main; namespace Chill\Migrations\Main;
@ -9,6 +16,12 @@ use Doctrine\Migrations\AbstractMigration;
final class Version20220413154743 extends AbstractMigration final class Version20220413154743 extends AbstractMigration
{ {
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails');
$this->addSql('ALTER TABLE chill_main_notification DROP accessKey');
}
public function getDescription(): string public function getDescription(): string
{ {
return 'add access keys and emails dest to notifications'; return 'add access keys and emails dest to notifications';
@ -30,10 +43,4 @@ final class Version20220413154743 extends AbstractMigration
$this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT'); $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT');
$this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL'); $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL');
} }
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails');
$this->addSql('ALTER TABLE chill_main_notification DROP accessKey');
}
} }