mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-23 22:28:07 +00:00
Merge branch 'task/1421-backend-droits-pour-les-tickets-visualiser-modifier-supprimer' into 'ticket-app-master'
Ajout de permissions sur le module Ticket See merge request Chill-Projet/chill-bundles!975
This commit is contained in:
@@ -133,6 +133,9 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 5, nullable: false, options: ['default' => 'fr'])]
|
||||
private string $locale = 'fr';
|
||||
|
||||
#[ORM\ManyToMany(targetEntity: UserGroup::class, mappedBy: 'users')]
|
||||
private Collection&Selectable $groupsAsMember;
|
||||
|
||||
/**
|
||||
* User constructor.
|
||||
*/
|
||||
@@ -141,6 +144,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
$this->groupCenters = new ArrayCollection();
|
||||
$this->scopeHistories = new ArrayCollection();
|
||||
$this->jobHistories = new ArrayCollection();
|
||||
$this->groupsAsMember = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
@@ -170,6 +174,32 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
return $this->absenceEnd;
|
||||
}
|
||||
|
||||
public function addGroupAsMember(UserGroup $userGroup): self
|
||||
{
|
||||
if (!$this->groupsAsMember->contains($userGroup)) {
|
||||
$this->groupsAsMember->add($userGroup);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeGroupAsMember(UserGroup $userGroup): self
|
||||
{
|
||||
if ($this->groupsAsMember->contains($userGroup)) {
|
||||
$this->groupsAsMember->removeElement($userGroup);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Selectable&Collection<int, UserGroup>
|
||||
*/
|
||||
public function getGroupsAsMember(): Collection&Selectable
|
||||
{
|
||||
return $this->groupsAsMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes.
|
||||
*
|
||||
|
||||
@@ -54,7 +54,7 @@ class UserGroup
|
||||
/**
|
||||
* @var Collection<int, User>&Selectable<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'groupsAsMember')]
|
||||
#[ORM\JoinTable(name: 'chill_main_user_group_user')]
|
||||
private Collection&Selectable $users;
|
||||
|
||||
@@ -129,6 +129,7 @@ class UserGroup
|
||||
{
|
||||
if (!$this->users->contains($user)) {
|
||||
$this->users[] = $user;
|
||||
$user->addGroupAsMember($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -138,6 +139,7 @@ class UserGroup
|
||||
{
|
||||
if ($this->users->contains($user)) {
|
||||
$this->users->removeElement($user);
|
||||
$user->removeGroupAsMember($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Chill\MainBundle\Security\Authorization;
|
||||
interface VoterGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @param string $class The FQDN of a class
|
||||
* @param array $attributes an array of attributes
|
||||
* @param string|null $class The FQDN of a class
|
||||
* @param array $attributes an array of attributes
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
|
||||
50
src/Bundle/ChillMainBundle/Tests/Entity/UserGroupTest.php
Normal file
50
src/Bundle/ChillMainBundle/Tests/Entity/UserGroupTest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Tests\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class UserGroupTest extends TestCase
|
||||
{
|
||||
public function testAddUser(): void
|
||||
{
|
||||
$userGroup = new UserGroup();
|
||||
$user = new User();
|
||||
|
||||
$userGroup->addUser($user);
|
||||
|
||||
self::assertTrue($userGroup->getUsers()->contains($user));
|
||||
self::assertTrue($user->getGroupsAsMember()->contains($userGroup));
|
||||
}
|
||||
|
||||
public function testRemoveUser(): void
|
||||
{
|
||||
$userGroup = new UserGroup();
|
||||
$user = new User();
|
||||
|
||||
$userGroup->addUser($user);
|
||||
self::assertTrue($userGroup->getUsers()->contains($user));
|
||||
self::assertTrue($user->getGroupsAsMember()->contains($userGroup));
|
||||
|
||||
$userGroup->removeUser($user);
|
||||
|
||||
self::assertFalse($userGroup->getUsers()->contains($user));
|
||||
self::assertFalse($user->getGroupsAsMember()->contains($userGroup));
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Repository\CountryRepository;
|
||||
use Chill\MainBundle\Search\ParsingException;
|
||||
use Chill\MainBundle\Search\SearchApiQuery;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
@@ -32,7 +31,6 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $em,
|
||||
private CountryRepository $countryRepository,
|
||||
private AuthorizationHelperInterface $authorizationHelper,
|
||||
private PersonIdentifierManagerInterface $personIdentifierManager,
|
||||
) {}
|
||||
|
||||
@@ -15,6 +15,7 @@ use Chill\TicketBundle\Action\Ticket\AddCommentCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\AddCommentCommandHandler;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\CommentVoter;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -38,7 +39,7 @@ final readonly class AddCommentController
|
||||
#[Route('/api/1.0/ticket/{id}/comment/add', name: 'chill_ticket_comment_add', methods: ['POST'])]
|
||||
public function __invoke(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only user can add ticket comments.');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
@@ -41,7 +41,7 @@ final readonly class CenterForTicketListApiController
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$centers = $this->authorizationHelperForCurrentUser->getReachableCenters(PersonVoter::SEE);
|
||||
$centers = $this->authorizationHelperForCurrentUser->getReachableCenters(TicketVoter::READ);
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($centers, 'json', ['groups' => 'read']),
|
||||
|
||||
@@ -15,6 +15,7 @@ use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -38,8 +39,8 @@ final readonly class ChangeEmergencyStateApiController
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/emergency/yes', name: 'chill_ticket_ticket_emergency_yes_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function setEmergencyYes(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to set emergency status to YES.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to set emergency status to YES.');
|
||||
}
|
||||
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);
|
||||
@@ -56,8 +57,8 @@ final readonly class ChangeEmergencyStateApiController
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/emergency/no', name: 'chill_ticket_ticket_emergency_no_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function setEmergencyNo(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to set emergency status to NO.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to set emergency status to NO.');
|
||||
}
|
||||
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::NO);
|
||||
|
||||
@@ -15,6 +15,7 @@ use Chill\TicketBundle\Action\Ticket\ChangeStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeStateCommandHandler;
|
||||
use Chill\TicketBundle\Entity\StateEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -38,8 +39,8 @@ final readonly class ChangeStateApiController
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/close', name: 'chill_ticket_ticket_close_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function close(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to close tickets.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to close tickets.');
|
||||
}
|
||||
|
||||
$command = new ChangeStateCommand(StateEnum::CLOSED);
|
||||
@@ -56,8 +57,8 @@ final readonly class ChangeStateApiController
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/open', name: 'chill_ticket_ticket_open_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function open(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to open tickets.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to open tickets.');
|
||||
}
|
||||
|
||||
$command = new ChangeStateCommand(StateEnum::OPEN);
|
||||
|
||||
@@ -16,6 +16,7 @@ use Chill\TicketBundle\Action\Ticket\Handler\AssociateByPhonenumberCommandHandle
|
||||
use Chill\TicketBundle\Action\Ticket\CreateTicketCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\CreateTicketCommandHandler;
|
||||
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -39,8 +40,8 @@ final readonly class CreateTicketController
|
||||
#[Route('{_locale}/ticket/ticket/create')]
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to create tickets.');
|
||||
if (!$this->security->isGranted(TicketVoter::CREATE)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to create tickets.');
|
||||
}
|
||||
|
||||
if ('' !== $extId = $request->query->get('extId', '')) {
|
||||
|
||||
@@ -12,17 +12,21 @@ declare(strict_types=1);
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Twig\Environment;
|
||||
|
||||
class EditTicketController
|
||||
final readonly class EditTicketController
|
||||
{
|
||||
private readonly string $personPerTicket;
|
||||
|
||||
public function __construct(
|
||||
private readonly Environment $templating,
|
||||
private Environment $templating,
|
||||
private Security $security,
|
||||
ParameterBagInterface $parameterBag,
|
||||
) {
|
||||
$this->personPerTicket = $parameterBag->get('chill_ticket')['ticket']['person_per_ticket'];
|
||||
@@ -32,6 +36,10 @@ class EditTicketController
|
||||
public function __invoke(
|
||||
Ticket $ticket,
|
||||
): Response {
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Access denied');
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillTicket/Ticket/edit.html.twig',
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Chill\TicketBundle\Controller;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -38,8 +39,8 @@ final readonly class ReplaceMotiveController
|
||||
#[Route('/api/1.0/ticket/{id}/motive/set', name: 'chill_ticket_motive_set', methods: ['POST'])]
|
||||
public function __invoke(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Access denied');
|
||||
}
|
||||
|
||||
$command = $this->serializer->deserialize($request->getContent(), ReplaceMotiveCommand::class, 'json', [
|
||||
|
||||
@@ -15,6 +15,7 @@ use Chill\TicketBundle\Action\Ticket\AddAddresseeCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\SetAddresseesCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\SetAddresseesCommand;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -39,7 +40,7 @@ final readonly class SetAddresseesController
|
||||
#[Route('/api/1.0/ticket/{id}/addressees/set', methods: ['POST'])]
|
||||
public function setAddressees(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only users can set addressees.');
|
||||
}
|
||||
|
||||
@@ -51,8 +52,8 @@ final readonly class SetAddresseesController
|
||||
#[Route('/api/1.0/ticket/{id}/addressee/add', methods: ['POST'])]
|
||||
public function addAddressee(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users can add addressees.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users can add addressees.');
|
||||
}
|
||||
|
||||
$command = $this->serializer->deserialize($request->getContent(), AddAddresseeCommand::class, 'json', [AbstractNormalizer::GROUPS => ['read']]);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Chill\TicketBundle\Controller;
|
||||
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -39,8 +40,8 @@ final readonly class SetCallerApiController
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/set-caller', name: 'chill_ticket_ticket_set_caller_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function setCaller(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to set ticket callers.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to set ticket callers.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Chill\TicketBundle\Controller;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\SetPersonsCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\SetPersonsCommand;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -38,8 +39,8 @@ final readonly class SetPersonsController
|
||||
#[Route('/api/1.0/ticket/{id}/persons/set', methods: ['POST'])]
|
||||
public function setPersons(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users can set addressees.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users can set addressees.');
|
||||
}
|
||||
|
||||
$command = $this->serializer->deserialize($request->getContent(), SetPersonsCommand::class, 'json', [AbstractNormalizer::GROUPS => ['read']]);
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Chill\TicketBundle\Service\Ticket\SuggestPersonForTicketInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -31,8 +32,8 @@ final readonly class SuggestPersonForTicketApiController
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/suggest-person', name: 'chill_ticket_ticket_suggest_person_api', requirements: ['id' => '\d+'], methods: ['GET'])]
|
||||
public function __invoke(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to suggest persons for tickets.');
|
||||
if (!$this->security->isGranted(TicketVoter::WRITE, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Only allowed users are allowed to suggest persons for tickets.');
|
||||
}
|
||||
|
||||
$persons = $this->suggestPersonForTicket->suggestPerson($ticket, 0, 10);
|
||||
|
||||
@@ -12,17 +12,27 @@ declare(strict_types=1);
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
class TicketControllerApi
|
||||
final readonly class TicketControllerApi
|
||||
{
|
||||
public function __construct(private readonly SerializerInterface $serializer) {}
|
||||
public function __construct(
|
||||
private SerializerInterface $serializer,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
#[Route('/api/1.0/ticket/ticket/{id}', requirements: ['id' => '\d+'], methods: ['GET'])]
|
||||
public function get(Ticket $ticket): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted(TicketVoter::READ, $ticket)) {
|
||||
throw new AccessDeniedHttpException('Access denied');
|
||||
}
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($ticket, 'json', ['groups' => ['read']]),
|
||||
json: true
|
||||
|
||||
@@ -22,6 +22,7 @@ use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\StateEnum;
|
||||
use Chill\TicketBundle\Repository\MotiveRepository;
|
||||
use Chill\TicketBundle\Repository\TicketACLAwareRepositoryInterface;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
@@ -55,8 +56,8 @@ final readonly class TicketListApiController
|
||||
#[Route('/api/1.0/ticket/ticket/list', name: 'chill_ticket_list_api', methods: ['GET'])]
|
||||
public function listTicket(Request $request): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to list tickets.');
|
||||
if (!$this->security->isGranted(TicketVoter::READ)) {
|
||||
throw new AccessDeniedHttpException('only allowed user can access this page');
|
||||
}
|
||||
|
||||
$params = [];
|
||||
|
||||
@@ -11,8 +11,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
@@ -30,8 +30,8 @@ final readonly class TicketListController
|
||||
#[Route('/{_locale}/ticket/ticket/list', name: 'chill_ticket_ticket_list')]
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('only user can access this page');
|
||||
if (!$this->security->isGranted(TicketVoter::READ)) {
|
||||
throw new AccessDeniedHttpException('only allowed user can access this page');
|
||||
}
|
||||
|
||||
return new Response(
|
||||
@@ -42,7 +42,7 @@ final readonly class TicketListController
|
||||
#[Route('/{_locale}/ticket/by-person/{id}/list', name: 'chill_person_ticket_list')]
|
||||
public function listByPerson(Request $request, Person $person): Response
|
||||
{
|
||||
if (!$this->security->isGranted(PersonVoter::SEE, $person)) {
|
||||
if (!$this->security->isGranted(TicketVoter::READ, $person)) {
|
||||
throw new AccessDeniedHttpException('you are not allowed to see this person');
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use Chill\TicketBundle\Controller\Admin\MotiveController;
|
||||
use Chill\TicketBundle\Controller\MotiveApiController;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Form\MotiveType;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||
@@ -39,6 +40,12 @@ class ChillTicketExtension extends Extension implements PrependExtensionInterfac
|
||||
{
|
||||
$this->prependApi($container);
|
||||
$this->prependCruds($container);
|
||||
|
||||
$container->prependExtensionConfig('security', [
|
||||
'role_hierarchy' => [
|
||||
TicketVoter::WRITE => [TicketVoter::CREATE, TicketVoter::READ],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function prependApi(ContainerBuilder $container): void
|
||||
|
||||
@@ -333,4 +333,19 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
return $this->callerHistories;
|
||||
}
|
||||
|
||||
public function containsAddressee(User $user): bool
|
||||
{
|
||||
foreach ($this->getCurrentAddressee() as $addressee) {
|
||||
if ($addressee instanceof User && $addressee === $user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($addressee instanceof UserGroup && $addressee->contains($user)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ declare(strict_types=1);
|
||||
namespace Chill\TicketBundle\Menu;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
@@ -47,7 +47,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
/** @var Person $person */
|
||||
$person = $parameters['person'];
|
||||
|
||||
if ($this->authorizationChecker->isGranted(PersonVoter::SEE, $person)) {
|
||||
if ($this->authorizationChecker->isGranted(TicketVoter::READ, $person)) {
|
||||
$menu->addChild($this->translator->trans('chill_ticket.list.title_menu'), [
|
||||
'route' => 'chill_person_ticket_list',
|
||||
'routeParameters' => [
|
||||
|
||||
@@ -11,19 +11,27 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\MainBundle\Security\ChillSecurity;
|
||||
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
|
||||
use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||
use Chill\TicketBundle\Entity\CallerHistory;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\StateHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
final readonly class TicketACLAwareRepository implements TicketACLAwareRepositoryInterface
|
||||
{
|
||||
public function __construct(private EntityManagerInterface $em) {}
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private ChillSecurity $security,
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
|
||||
) {}
|
||||
|
||||
public function findTickets(array $params, int $start = 0, int $limit = 100): array
|
||||
{
|
||||
@@ -36,18 +44,62 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor
|
||||
// most recent tickets first
|
||||
$query->addOrderBy('t.createdAt', 'DESC');
|
||||
|
||||
$this->appendACLQuery($query);
|
||||
|
||||
return $query->getQuery()
|
||||
->setFirstResult($start)
|
||||
->setMaxResults($limit)
|
||||
->getResult();
|
||||
}
|
||||
|
||||
private function appendACLQuery(QueryBuilder $qb): void
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
$groups = $user->getGroupsAsMember();
|
||||
$centers = $this->authorizationHelperForCurrentUser->getReachableCenters(TicketVoter::READ);
|
||||
|
||||
$orx = $qb->expr()->orX(
|
||||
't.createdBy = :user',
|
||||
$qb->expr()->exists('SELECT 1 FROM '.AddresseeHistory::class.' ag_acl
|
||||
WHERE ag_acl.ticket = t AND ag_acl.endDate IS NULL AND (ag_acl.addresseeUser = :user OR ag_acl.addresseeGroup IN (:groups))'),
|
||||
$qb->expr()->exists('SELECT 1 FROM '.PersonHistory::class.' ph_acl JOIN '.PersonCenterHistory::class.' ph_acl_person_history WITH ph_acl_person_history.person = ph_acl.person
|
||||
WHERE ph_acl.ticket = t AND ph_acl.endDate IS NULL and ph_acl_person_history.endDate IS NULL AND ph_acl_person_history.center IN (:centers)'),
|
||||
$qb->expr()->exists('SELECT 1 FROM '.CallerHistory::class.' ch_acl JOIN '.PersonCenterHistory::class.' ch_acl_person_history WITH ch_acl_person_history.person = ch_acl.person
|
||||
WHERE ch_acl.ticket = t AND ch_acl.endDate IS NULL and ch_acl_person_history.endDate IS NULL AND ch_acl_person_history.center IN (:centers)'),
|
||||
);
|
||||
|
||||
$qb->andWhere($orx)
|
||||
->setParameter('user', $user)
|
||||
->setParameter('groups', $groups)
|
||||
->setParameter('centers', $centers);
|
||||
}
|
||||
|
||||
public function countTickets(array $params): int
|
||||
{
|
||||
return $this->buildQuery($params)->select('COUNT(t)')->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
private function buildQuery(array $params): QueryBuilder
|
||||
/**
|
||||
* Builds a Doctrine QueryBuilder object based on the given parameters to filter
|
||||
* tickets by various conditions such as ticket ID, associated persons, current states,
|
||||
* emergencies, motives, creation dates, creators, addressees, and related centers.
|
||||
*
|
||||
* @param array $params An associative array of query filters. Supported keys include:
|
||||
* - 'byTicketId' (array): Filters tickets by IDs.
|
||||
* - 'byPerson' (array): Filters tickets associated with specific persons.
|
||||
* - 'byCurrentState' (array): Filters tickets in specific states.
|
||||
* - 'byCurrentStateEmergency' (array): Filters tickets with specific emergency statuses.
|
||||
* - 'byMotives' (array): Filters tickets by motives including their descendants.
|
||||
* - 'byCreatedAfter' (\DateTime): Filters tickets created on or after the specified date.
|
||||
* - 'byCreatedBefore' (\DateTime): Filters tickets created on or before the specified date.
|
||||
* - 'byCreator' (array): Filters tickets created by specific users.
|
||||
* - 'byAddressee' (array): Filters tickets addressed to specific users or groups.
|
||||
* - 'byAddresseeGroup' (array): Filters tickets addressed to specific groups.
|
||||
* - 'byPersonCenter' (array): Filters tickets associated with specific person centers.
|
||||
*
|
||||
* @return QueryBuilder the built Doctrine QueryBuilder object containing the applied filters
|
||||
*/
|
||||
public function buildQuery(array $params): QueryBuilder
|
||||
{
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb->from(Ticket::class, 't');
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Security\Resolver;
|
||||
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\MainBundle\Security\Resolver\ManagerAwareCenterResolverInterface;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
class TicketCenterResolver implements CenterResolverInterface, ManagerAwareCenterResolverInterface
|
||||
{
|
||||
private ?CenterResolverManagerInterface $manager = null;
|
||||
|
||||
public static function getDefaultPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function resolveCenter($entity, ?array $options = [])
|
||||
{
|
||||
assert($entity instanceof Ticket);
|
||||
|
||||
$centers = [];
|
||||
foreach ($entity->getPersons() as $person) {
|
||||
foreach ($this->manager->resolveCenters($person, $options) as $center) {
|
||||
$centers[spl_object_hash($center)] = $center;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($centers);
|
||||
}
|
||||
|
||||
public function supports($entity, ?array $options = []): bool
|
||||
{
|
||||
return $entity instanceof Ticket;
|
||||
}
|
||||
|
||||
public function setResolverManager(CenterResolverManagerInterface $manager): void
|
||||
{
|
||||
$this->manager = $manager;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Security\Voter;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
@@ -18,20 +23,58 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
/**
|
||||
* Check permission on Ticket.
|
||||
*/
|
||||
final class TicketVoter extends Voter
|
||||
final class TicketVoter extends Voter implements ProvideRoleHierarchyInterface
|
||||
{
|
||||
public const WRITE = 'CHILL_TICKET_TICKET_WRITE';
|
||||
|
||||
public const CREATE = 'CHILL_TICKET_TICKET_CREATE';
|
||||
public const READ = 'CHILL_TICKET_TICKET_READ';
|
||||
|
||||
private const ALL = [self::WRITE, self::READ];
|
||||
private const ALL = [self::WRITE, self::READ, self::CREATE];
|
||||
|
||||
private readonly VoterHelperInterface $voterHelper;
|
||||
|
||||
public function __construct(VoterHelperFactoryInterface $voterHelperFactory)
|
||||
{
|
||||
$this->voterHelper = $voterHelperFactory->generate(self::class)
|
||||
->addCheckFor(Ticket::class, self::ALL)
|
||||
->addCheckFor(null, [self::CREATE, self::READ])
|
||||
->addCheckFor(Person::class, [self::READ])
|
||||
->build();
|
||||
}
|
||||
|
||||
protected function supports(string $attribute, $subject): bool
|
||||
{
|
||||
return $subject instanceof Ticket && in_array($attribute, self::ALL, true);
|
||||
return $this->voterHelper->supports($attribute, $subject);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
return true;
|
||||
assert($subject instanceof Ticket || null === $subject);
|
||||
|
||||
$user = $token->getUser();
|
||||
|
||||
if ($subject instanceof Ticket && $user instanceof User && ($user === $subject->getCreatedBy() || $subject->containsAddressee($user))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
|
||||
}
|
||||
|
||||
public function getRolesWithHierarchy(): array
|
||||
{
|
||||
return [
|
||||
'ticket.permission.title' => [self::READ, self::WRITE],
|
||||
];
|
||||
}
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
return [self::WRITE, self::READ];
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope(): array
|
||||
{
|
||||
return [self::WRITE, self::READ];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ services:
|
||||
Chill\TicketBundle\Security\Authorization\:
|
||||
resource: '../Security/Authorization/'
|
||||
|
||||
Chill\TicketBundle\Security\Resolver\:
|
||||
resource: '../Security/Resolver/'
|
||||
|
||||
Chill\TicketBundle\Serializer\:
|
||||
resource: '../Serializer/'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ chill_ticket:
|
||||
list:
|
||||
title: "Tickets"
|
||||
title_with_name: "{name, select, null {Tickets} undefined {Tickets} other {Tickets de {name}}}"
|
||||
title_menu: "Tickets de l'usager"
|
||||
title_menu: "Tickets"
|
||||
show_all_history: "Afficher tout l'historique"
|
||||
title_previous_tickets: "{name, select, other {Précédent ticket de {name}} undefined {Précédent ticket}}"
|
||||
no_tickets: "Aucun ticket"
|
||||
@@ -187,3 +187,11 @@ crud:
|
||||
"No supplementary comments": "Aucun commentaire supplémentaire"
|
||||
"Back to the list": "Retour à la liste"
|
||||
"Edit": "Modifier"
|
||||
|
||||
# permissions
|
||||
ticket:
|
||||
permission:
|
||||
title: "Tickets"
|
||||
CHILL_TICKET_TICKET_WRITE: Modifier et créer des tickets
|
||||
CHILL_TICKET_TICKET_READ: Consulter les tickets
|
||||
CHILL_TICKET_TICKET_CREATE: Créer des tickets
|
||||
|
||||
@@ -16,6 +16,7 @@ use Chill\TicketBundle\Controller\AddCommentController;
|
||||
use Chill\TicketBundle\Entity\Comment;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\CommentVoter;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
@@ -83,7 +84,7 @@ class AddCommentControllerTest extends KernelTestCase
|
||||
private function buildController(bool $willFlush): AddCommentController
|
||||
{
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, Argument::type(Ticket::class))->willReturn(true);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Controller\ChangeEmergencyStateApiController;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
@@ -38,7 +39,7 @@ final class ChangeEmergencyStateApiControllerTest extends TestCase
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(false);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
@@ -58,9 +59,8 @@ final class ChangeEmergencyStateApiControllerTest extends TestCase
|
||||
public function testSetEmergencyYesWithPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$changeEmergencyStateCommandHandler->__invoke(
|
||||
@@ -92,7 +92,7 @@ final class ChangeEmergencyStateApiControllerTest extends TestCase
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(false);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
@@ -112,9 +112,8 @@ final class ChangeEmergencyStateApiControllerTest extends TestCase
|
||||
public function testSetEmergencyNoWithPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$changeEmergencyStateCommandHandler->__invoke(
|
||||
|
||||
@@ -16,6 +16,7 @@ use Chill\TicketBundle\Action\Ticket\Handler\ChangeStateCommandHandler;
|
||||
use Chill\TicketBundle\Controller\ChangeStateApiController;
|
||||
use Chill\TicketBundle\Entity\StateEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
@@ -38,7 +39,7 @@ final class ChangeStateApiControllerTest extends TestCase
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(false);
|
||||
|
||||
$changeStateCommandHandler = $this->prophesize(ChangeStateCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
@@ -58,9 +59,8 @@ final class ChangeStateApiControllerTest extends TestCase
|
||||
public function testCloseWithPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$changeStateCommandHandler = $this->prophesize(ChangeStateCommandHandler::class);
|
||||
$changeStateCommandHandler->__invoke(
|
||||
@@ -92,7 +92,7 @@ final class ChangeStateApiControllerTest extends TestCase
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(false);
|
||||
|
||||
$changeStateCommandHandler = $this->prophesize(ChangeStateCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
@@ -112,9 +112,8 @@ final class ChangeStateApiControllerTest extends TestCase
|
||||
public function testOpenWithPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$changeStateCommandHandler = $this->prophesize(ChangeStateCommandHandler::class);
|
||||
$changeStateCommandHandler->__invoke(
|
||||
|
||||
@@ -17,6 +17,7 @@ use Chill\TicketBundle\Entity\Motive;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Chill\TicketBundle\Controller\ReplaceMotiveController;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -41,7 +42,7 @@ class ReplaceMotiveControllerTest extends TestCase
|
||||
|
||||
// Mock Security
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
// Mock EntityManager
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
|
||||
@@ -18,6 +18,7 @@ use Chill\TicketBundle\Action\Ticket\SetAddresseesCommand;
|
||||
use Chill\TicketBundle\Controller\SetAddresseesController;
|
||||
use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
@@ -179,7 +180,7 @@ class SetAddresseesControllerTest extends KernelTestCase
|
||||
{
|
||||
$user = new User();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, Argument::type(Ticket::class))->willReturn(true);
|
||||
$security->getUser()->willReturn($user);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
|
||||
@@ -17,6 +17,7 @@ use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
|
||||
use Chill\TicketBundle\Controller\SetCallerApiController;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Voter\TicketVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
@@ -42,7 +43,7 @@ final class SetCallerApiControllerTest extends TestCase
|
||||
$request = new Request();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(false);
|
||||
|
||||
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
@@ -65,7 +66,7 @@ final class SetCallerApiControllerTest extends TestCase
|
||||
$request = new Request([], [], [], [], [], [], 'invalid json');
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
@@ -94,7 +95,7 @@ final class SetCallerApiControllerTest extends TestCase
|
||||
$command = new SetCallerCommand($person);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->deserialize('{"caller": {"id": 123, "type": "person"}}', SetCallerCommand::class, 'json')
|
||||
@@ -132,7 +133,7 @@ final class SetCallerApiControllerTest extends TestCase
|
||||
$command = new SetCallerCommand($thirdParty);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->deserialize('{"caller": {"id": 456, "type": "thirdParty"}}', SetCallerCommand::class, 'json')
|
||||
@@ -169,7 +170,7 @@ final class SetCallerApiControllerTest extends TestCase
|
||||
$command = new SetCallerCommand(null);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$security->isGranted(TicketVoter::WRITE, $ticket)->willReturn(true);
|
||||
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->deserialize('{"caller": null}', SetCallerCommand::class, 'json')
|
||||
|
||||
@@ -158,4 +158,42 @@ class TicketTest extends KernelTestCase
|
||||
self::assertCount(2, $ticket->getEmergencyStatusHistories());
|
||||
self::assertSame(EmergencyStatusEnum::NO, $ticket->getEmergencyStatus());
|
||||
}
|
||||
|
||||
public function testContainsAddressee(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$user1 = new User();
|
||||
$user2 = new User();
|
||||
$user3 = new User();
|
||||
$group = new UserGroup();
|
||||
|
||||
// Initial state
|
||||
self::assertFalse($ticket->containsAddressee($user1));
|
||||
|
||||
// user1 is direct addressee
|
||||
$history1 = new AddresseeHistory($user1, new \DateTimeImmutable(), $ticket);
|
||||
|
||||
// group is addressee, and user2 is in group
|
||||
$history2 = new AddresseeHistory($group, new \DateTimeImmutable(), $ticket);
|
||||
|
||||
// user3 is not addressee
|
||||
self::assertTrue($ticket->containsAddressee($user1));
|
||||
self::assertFalse($ticket->containsAddressee($user2)); // Not in group yet
|
||||
self::assertFalse($ticket->containsAddressee($user3));
|
||||
|
||||
// Add user2 to group
|
||||
$group->addUser($user2);
|
||||
self::assertTrue($ticket->containsAddressee($user2));
|
||||
|
||||
// End user1 history
|
||||
$history1->setEndDate(new \DateTimeImmutable());
|
||||
self::assertFalse($ticket->containsAddressee($user1));
|
||||
|
||||
// user2 is still in via group
|
||||
self::assertTrue($ticket->containsAddressee($user2));
|
||||
|
||||
// End group history
|
||||
$history2->setEndDate(new \DateTimeImmutable());
|
||||
self::assertFalse($ticket->containsAddressee($user2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Tests\Security\Resolver;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Security\Resolver\TicketCenterResolver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class TicketCenterResolverTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private TicketCenterResolver $resolver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->resolver = new TicketCenterResolver();
|
||||
}
|
||||
|
||||
public function testSupports(): void
|
||||
{
|
||||
$ticket = $this->prophesize(Ticket::class)->reveal();
|
||||
$otherEntity = new \stdClass();
|
||||
|
||||
$this->assertTrue($this->resolver->supports($ticket));
|
||||
$this->assertFalse($this->resolver->supports($otherEntity));
|
||||
}
|
||||
|
||||
public function testGetDefaultPriority(): void
|
||||
{
|
||||
$this->assertSame(0, TicketCenterResolver::getDefaultPriority());
|
||||
}
|
||||
|
||||
public function testResolveCenterWithNoPersons(): void
|
||||
{
|
||||
$ticket = $this->prophesize(Ticket::class);
|
||||
$ticket->getPersons()->willReturn([]);
|
||||
|
||||
$manager = $this->prophesize(CenterResolverManagerInterface::class);
|
||||
$this->resolver->setResolverManager($manager->reveal());
|
||||
|
||||
$result = $this->resolver->resolveCenter($ticket->reveal());
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
public function testResolveCenterWithPersons(): void
|
||||
{
|
||||
$person1 = $this->prophesize(Person::class)->reveal();
|
||||
$person2 = $this->prophesize(Person::class)->reveal();
|
||||
|
||||
$ticket = $this->prophesize(Ticket::class);
|
||||
$ticket->getPersons()->willReturn([$person1, $person2]);
|
||||
|
||||
$center1 = new Center();
|
||||
$center2 = new Center();
|
||||
|
||||
$options = ['some' => 'option'];
|
||||
|
||||
$manager = $this->prophesize(CenterResolverManagerInterface::class);
|
||||
$manager->resolveCenters($person1, $options)->willReturn([$center1]);
|
||||
$manager->resolveCenters($person2, $options)->willReturn([$center1, $center2]);
|
||||
|
||||
$this->resolver->setResolverManager($manager->reveal());
|
||||
|
||||
$result = $this->resolver->resolveCenter($ticket->reveal(), $options);
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
$this->assertContains($center1, $result);
|
||||
$this->assertContains($center2, $result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user