mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
[Api] Secure the customer get endpoint
This commit is contained in:
parent
7efc67e18b
commit
a30537ec7f
4 changed files with 275 additions and 2 deletions
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\Customer;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
|
||||
final readonly class ShopUserBasedExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private SectionProviderInterface $sectionProvider,
|
||||
private UserContextInterface $userContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $context
|
||||
*/
|
||||
public function applyToCollection(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): void {
|
||||
$this->filterOutOrders($queryBuilder, $queryNameGenerator, $resourceClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $identifiers
|
||||
* @param array<array-key, mixed> $context
|
||||
*/
|
||||
public function applyToItem(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
array $identifiers,
|
||||
?Operation $operation = null,
|
||||
array $context = [],
|
||||
): void {
|
||||
$this->filterOutOrders($queryBuilder, $queryNameGenerator, $resourceClass);
|
||||
}
|
||||
|
||||
private function filterOutOrders(
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
): void {
|
||||
if (!is_a($resourceClass, CustomerInterface::class, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->sectionProvider->getSection() instanceof ShopApiSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->userContext->getUser();
|
||||
|
||||
$rootAlias = $queryBuilder->getRootAliases()[0];
|
||||
$userJoinAlias = $queryNameGenerator->generateJoinAlias('user');
|
||||
$userParameterName = $queryNameGenerator->generateParameterName('user');
|
||||
|
||||
$queryBuilder
|
||||
->innerJoin(
|
||||
sprintf('%s.user', $rootAlias),
|
||||
$userJoinAlias,
|
||||
Join::WITH,
|
||||
$queryBuilder->expr()->eq(sprintf('%s.id', $userJoinAlias), sprintf(':%s', $userParameterName))
|
||||
)
|
||||
->setParameter($userParameterName, $user)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -198,5 +198,15 @@
|
|||
<tag name="api_platform.doctrine.orm.query_extension.collection" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.item" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
id="sylius_api.doctrine.orm.query_extension.shop.customer.shop_user_based"
|
||||
class="Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\Customer\ShopUserBasedExtension"
|
||||
>
|
||||
<argument type="service" id="sylius.section_resolver.uri_based_section_resolver" />
|
||||
<argument type="service" id="Sylius\Bundle\ApiBundle\Context\UserContextInterface" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.collection" />
|
||||
<tag name="api_platform.doctrine.orm.query_extension.item" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\Doctrine\ORM\QueryExtension\Shop\Customer;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Context\UserContextInterface;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\AdminApiSection;
|
||||
use Sylius\Bundle\ApiBundle\SectionResolver\ShopApiSection;
|
||||
use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface;
|
||||
use Sylius\Component\Core\Model\AdminUserInterface;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Resource\Model\ResourceInterface;
|
||||
|
||||
final class ShopUserBasedExtensionSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
UserContextInterface $userContext,
|
||||
): void {
|
||||
$this->beConstructedWith($sectionProvider, $userContext);
|
||||
}
|
||||
|
||||
function it_does_not_apply_conditions_to_collection_for_unsupported_resource(
|
||||
UserContextInterface $userContext,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
): void {
|
||||
$userContext->getUser()->shouldNotBeCalled();
|
||||
$queryBuilder->getRootAliases()->shouldNotBeCalled();
|
||||
|
||||
$this->applyToCollection($queryBuilder, $queryNameGenerator, ResourceInterface::class, new Get());
|
||||
}
|
||||
|
||||
function it_does_not_apply_conditions_to_collection_for_admin_api_section(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
UserContextInterface $userContext,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
AdminApiSection $section,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn($section);
|
||||
$userContext->getUser()->shouldNotBeCalled();
|
||||
$queryBuilder->getRootAliases()->shouldNotBeCalled();
|
||||
|
||||
$this->applyToCollection($queryBuilder, $queryNameGenerator, CustomerInterface::class, new Get());
|
||||
}
|
||||
|
||||
function it_applies_conditions_to_collection(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
UserContextInterface $userContext,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
ShopApiSection $section,
|
||||
ShopUserInterface $user,
|
||||
Expr $expr,
|
||||
Expr\Func $exprEq,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn($section);
|
||||
$userContext->getUser()->willReturn($user);
|
||||
|
||||
$queryBuilder->getRootAliases()->willReturn(['o']);
|
||||
$queryNameGenerator->generateJoinAlias('user')->willReturn('user');
|
||||
$queryNameGenerator->generateParameterName('user')->willReturn('user');
|
||||
|
||||
$queryBuilder->expr()->willReturn($expr);
|
||||
$expr->eq('user.id', ':user')->willReturn($exprEq);
|
||||
$queryBuilder->innerJoin('o.user', 'user', 'WITH', $exprEq)->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
|
||||
$queryBuilder->setParameter('user', $user)->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
|
||||
|
||||
$this->applyToCollection($queryBuilder, $queryNameGenerator, CustomerInterface::class, new Get());
|
||||
}
|
||||
|
||||
function it_does_not_apply_conditions_to_item_for_unsupported_resource(
|
||||
UserContextInterface $userContext,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
): void {
|
||||
$userContext->getUser()->shouldNotBeCalled();
|
||||
$queryBuilder->getRootAliases()->shouldNotBeCalled();
|
||||
|
||||
$this->applyToItem($queryBuilder, $queryNameGenerator, ResourceInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
function it_does_not_apply_conditions_to_item_for_admin_api_section(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
UserContextInterface $userContext,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
AdminApiSection $section,
|
||||
): void {
|
||||
$sectionProvider->getSection()->willReturn($section);
|
||||
$userContext->getUser()->shouldNotBeCalled();
|
||||
$queryBuilder->getRootAliases()->shouldNotBeCalled();
|
||||
|
||||
$this->applyToItem($queryBuilder, $queryNameGenerator, CustomerInterface::class, [], new Get());
|
||||
}
|
||||
|
||||
function it_applies_conditions_to_item(
|
||||
SectionProviderInterface $sectionProvider,
|
||||
UserContextInterface $userContext,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
ShopApiSection $section,
|
||||
ShopUserInterface $user,
|
||||
CustomerInterface $customer,
|
||||
Expr $expr,
|
||||
Expr\Func $exprEq,
|
||||
): void {
|
||||
$user->getCustomer()->willReturn($customer);
|
||||
$sectionProvider->getSection()->willReturn($section);
|
||||
$userContext->getUser()->willReturn($user);
|
||||
|
||||
$queryBuilder->getRootAliases()->willReturn(['o']);
|
||||
$queryNameGenerator->generateJoinAlias('user')->willReturn('user');
|
||||
$queryNameGenerator->generateParameterName('user')->willReturn('user');
|
||||
|
||||
$queryBuilder->expr()->willReturn($expr);
|
||||
$expr->eq('user.id', ':user')->willReturn($exprEq);
|
||||
$queryBuilder->innerJoin('o.user', 'user', 'WITH', $exprEq)->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
|
||||
$queryBuilder->setParameter('user', $user)->shouldBeCalled()->willReturn($queryBuilder->getWrappedObject());
|
||||
|
||||
$this->applyToItem($queryBuilder, $queryNameGenerator, CustomerInterface::class, [], new Get());
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,37 @@ final class CustomersTest extends JsonApiTestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_customer(): void
|
||||
public function it_gets_customer_as_logged_user(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles(['authentication/shop_user.yaml']);
|
||||
/** @var CustomerInterface $customer */
|
||||
$customer = $fixtures['customer_oliver'];
|
||||
|
||||
$this->requestGet(
|
||||
uri: '/api/v2/shop/customers/' . $customer->getId(),
|
||||
headers: $this->headerBuilder()->withShopUserAuthorization($customer->getEmailCanonical())->build(),
|
||||
);
|
||||
|
||||
$this->assertResponse($this->client->getResponse(), 'shop/customer/get_customer_response');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_customer_as_another_logged_user(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles(['authentication/shop_user.yaml']);
|
||||
/** @var CustomerInterface $customer */
|
||||
$customer = $fixtures['customer_oliver'];
|
||||
|
||||
$this->requestGet(
|
||||
uri: '/api/v2/shop/customers/' . $customer->getId(),
|
||||
headers: $this->headerBuilder()->withShopUserAuthorization('dave@doe.com')->build(),
|
||||
);
|
||||
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_customer_as_guest(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles(['authentication/shop_user.yaml']);
|
||||
/** @var CustomerInterface $customer */
|
||||
|
|
@ -39,7 +69,7 @@ final class CustomersTest extends JsonApiTestCase
|
|||
|
||||
$this->requestGet('/api/v2/shop/customers/' . $customer->getId());
|
||||
|
||||
$this->assertResponse($this->client->getResponse(), 'shop/customer/get_customer_response');
|
||||
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -95,6 +125,7 @@ final class CustomersTest extends JsonApiTestCase
|
|||
'birthday' => '2023-10-24T11:00:00.000Z',
|
||||
'subscribedToNewsletter' => true,
|
||||
],
|
||||
headers: $this->headerBuilder()->withShopUserAuthorization($customer->getEmailCanonical())->build(),
|
||||
);
|
||||
|
||||
$this->assertResponse($this->client->getResponse(), 'shop/customer/put_customer_response');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue