diff --git a/src/API/InvoiceController.php b/src/API/InvoiceController.php index f7dd6717..a8b7830c 100644 --- a/src/API/InvoiceController.php +++ b/src/API/InvoiceController.php @@ -18,6 +18,7 @@ use FOS\RestBundle\Request\ParamFetcherInterface; use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use OpenApi\Attributes as OA; +use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -76,6 +77,9 @@ final class InvoiceController extends BaseApiController /** @var array $customers */ $customers = $paramFetcher->get('customers'); foreach ($customerRepository->findByIds(array_unique($customers)) as $customer) { + if (!$this->isGranted('access', $customer)) { + throw $this->createAccessDeniedException('Cannot access Customer: ' . $customer->getId()); + } $query->addCustomer($customer); } @@ -90,6 +94,7 @@ final class InvoiceController extends BaseApiController * Fetch invoice */ #[IsGranted('view_invoice')] + #[IsGranted(new Expression("is_granted('access', subject.getCustomer())"), 'invoice')] #[OA\Response(response: 200, description: 'Returns one invoice', content: new OA\JsonContent(ref: '#/components/schemas/Invoice'))] #[Route(methods: ['GET'], path: '/{id}', name: 'get_invoice', requirements: ['id' => '\d+'])] public function getAction(Invoice $invoice): Response diff --git a/tests/API/InvoiceControllerTest.php b/tests/API/InvoiceControllerTest.php index e83b2d8b..bf1d8aff 100644 --- a/tests/API/InvoiceControllerTest.php +++ b/tests/API/InvoiceControllerTest.php @@ -9,8 +9,11 @@ namespace App\Tests\API; +use App\Entity\Customer; use App\Entity\Invoice; +use App\Entity\Team; use App\Entity\User; +use App\Repository\TeamRepository; use App\Tests\DataFixtures\InvoiceFixtures; use PHPUnit\Framework\Attributes\Group; @@ -123,4 +126,59 @@ class InvoiceControllerTest extends APIControllerBaseTestCase { $this->assertEntityNotFound(User::ROLE_USER, '/api/invoices/' . PHP_INT_MAX); } + + public function testDownloadRespectsCustomerPermission(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD); + + $invoices = $this->importInvoiceFixtures(1, [Invoice::STATUS_NEW]); + $invoice = $invoices[0]; + $customer = $invoice->getCustomer(); + self::assertInstanceOf(Customer::class, $customer); + + $this->assertAccessIsGranted($client, '/api/invoices/' . $invoice->getId()); + + $content = $client->getResponse()->getContent(); + self::assertIsString($content); + $result = json_decode($content, true); + + self::assertIsArray($result); + self::assertApiResponseTypeStructure('Invoice', $result); + + $team = new Team('foo'); + $team->addTeamlead($this->getUserByRole(User::ROLE_ADMIN)); + $team->addCustomer($customer); + + $em = $this->getEntityManager(); + /** @var TeamRepository $repository */ + $repository = $em->getRepository(Team::class); + $repository->saveTeam($team); + + $this->assertApiAccessDenied($client, '/api/invoices/' . $invoice->getId()); + } + + public function testCollectionRespectsCustomerPermission(): void + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD); + + $invoices = $this->importInvoiceFixtures(1, [Invoice::STATUS_NEW]); + $invoice = $invoices[0]; + $customer = $invoice->getCustomer(); + self::assertInstanceOf(Customer::class, $customer); + + $query = ['customers' => [$customer->getId()]]; + $this->assertAccessIsGranted($client, '/api/invoices', 'GET', $query); + + $team = new Team('foo'); + $team->addTeamlead($this->getUserByRole(User::ROLE_ADMIN)); + $team->addCustomer($customer); + + $em = $this->getEntityManager(); + /** @var TeamRepository $repository */ + $repository = $em->getRepository(Team::class); + $repository->saveTeam($team); + + $this->request($client, '/api/invoices', 'GET', $query); + $this->assertApiResponseAccessDenied($client->getResponse()); + } }