From ae0575e0a51d45dc3c3e8546bf790a657d10b9bb Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 14:55:33 +0100 Subject: [PATCH 01/14] Fixed the bug and added tests to prevent it --- .../Checkout/CompleteOrderAction.php | 18 ++++--- src/Exception/NotLoggedInException.php | 11 +++++ src/Handler/CompleteOrderHandler.php | 49 ++++++++++++++++--- .../config/services/handler/cart.xml | 4 +- .../CheckoutCompleteOrderApiTest.php | 49 +++++++++++++++++++ 5 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 src/Exception/NotLoggedInException.php diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index 70dcf89d9..c3d7a9845 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -9,6 +9,7 @@ use League\Tactician\CommandBus; use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; +use Sylius\ShopApiPlugin\Exception\NotLoggedInException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -34,12 +35,17 @@ public function __construct(ViewHandlerInterface $viewHandler, CommandBus $bus, public function __invoke(Request $request): Response { $email = $this->provideUserEmail($request); - - $this->bus->handle(new CompleteOrder( - $request->attributes->get('token'), - $email, - $request->request->get('notes') - )); + try { + $this->bus->handle( + new CompleteOrder( + $request->attributes->get('token'), + $email, + $request->request->get('notes') + ) + ); + }catch (NotLoggedInException $notLoggedInException) { + return $this->viewHandler->handle(View::create("You need to be logged in with the same user that wants to complete the order", Response::HTTP_UNAUTHORIZED)); + } return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); } diff --git a/src/Exception/NotLoggedInException.php b/src/Exception/NotLoggedInException.php new file mode 100644 index 000000000..6d6e318cb --- /dev/null +++ b/src/Exception/NotLoggedInException.php @@ -0,0 +1,11 @@ +orderRepository = $orderRepository; - $this->customerProvider = $customerProvider; + $this->customerRepository = $customerRepository; $this->stateMachineFactory = $stateMachineFactory; + $this->customerFactory = $customerFactory; + $this->loggedInUserProvider = $loggedInUserProvider; } public function handle(CompleteOrder $completeOrder) @@ -44,11 +58,30 @@ public function handle(CompleteOrder $completeOrder) Assert::true($stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE), sprintf('Order with %s token cannot be completed.', $completeOrder->orderToken())); - $customer = $this->customerProvider->provide($completeOrder->email()); - $order->setNotes($completeOrder->notes()); - $order->setCustomer($customer); + $order->setCustomer($this->getCustomer($completeOrder->email())); $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_COMPLETE); } + + private function getCustomer(string $emailAddress): CustomerInterface { + /** @var CustomerInterface|null $customer */ + $customer = $this->customerRepository->findOneBy(['email' => $emailAddress]); + + // If the customer does not exist then it's normal checkout + if ($customer === null) { + $customer = $this->customerFactory->createNew(); + $customer->setEmail($emailAddress); + + return $customer; + } + + // If the customer does exist the user has to be logged in with this customer. Otherwise the user is not authorized to complete the checkout + $loggedInUser = $this->loggedInUserProvider->provide(); + if($loggedInUser === null || $loggedInUser->getCustomer() !== $customer) { + throw new NotLoggedInException(); + } + + return $customer; + } } diff --git a/src/Resources/config/services/handler/cart.xml b/src/Resources/config/services/handler/cart.xml index f98d12942..c863a0190 100644 --- a/src/Resources/config/services/handler/cart.xml +++ b/src/Resources/config/services/handler/cart.xml @@ -101,7 +101,9 @@ - + + + diff --git a/tests/Controller/CheckoutCompleteOrderApiTest.php b/tests/Controller/CheckoutCompleteOrderApiTest.php index 49e8ca990..cfad66497 100644 --- a/tests/Controller/CheckoutCompleteOrderApiTest.php +++ b/tests/Controller/CheckoutCompleteOrderApiTest.php @@ -226,4 +226,53 @@ public function it_does_not_allow_to_complete_order_in_non_existent_channel() $response = $this->client->getResponse(); $this->assertResponse($response, 'channel_has_not_been_found_response', Response::HTTP_NOT_FOUND); } + + /** + * @test + */ + public function it_disallows_users_to_complete_checkout_for_someone_else() + { + $this->loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml', 'customer.yml']); + + $token = 'SDAOSLEFNWU35H3QLI5325'; + + /** @var CommandBus $bus */ + $bus = $this->get('tactician.commandbus'); + $bus->handle(new PickupCart($token, 'WEB_GB')); + $bus->handle(new PutSimpleItemToCart($token, 'LOGAN_MUG_CODE', 5)); + $bus->handle(new AddressOrder( + $token, + Address::createFromArray([ + 'firstName' => 'Sherlock', + 'lastName' => 'Holmes', + 'city' => 'London', + 'street' => 'Baker Street 221b', + 'countryCode' => 'GB', + 'postcode' => 'NWB', + 'provinceName' => 'Greater London', + ]), Address::createFromArray([ + 'firstName' => 'Sherlock', + 'lastName' => 'Holmes', + 'city' => 'London', + 'street' => 'Baker Street 221b', + 'countryCode' => 'GB', + 'postcode' => 'NWB', + 'provinceName' => 'Greater London', + ]) + )); + $bus->handle(new ChooseShippingMethod($token, 0, 'DHL')); + $bus->handle(new ChoosePaymentMethod($token, 0, 'PBC')); + $data = <<client->request('PUT', sprintf('/shop-api/checkout/%s/complete', $token), [], [], [ + 'CONTENT_TYPE' => 'application/json', + 'ACCEPT' => 'application/json', + ], $data); + $response = $this->client->getResponse(); + $this->assertResponseCode($response, Response::HTTP_UNAUTHORIZED); + } } From d4c81f6f46d0e40d9089502db211127d01441cab Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 16:26:12 +0100 Subject: [PATCH 02/14] Fixed phpspec --- spec/Handler/CompleteOrderHandlerSpec.php | 96 ++++++++++++++++--- .../Checkout/CompleteOrderAction.php | 10 +- src/Exception/NotLoggedInException.php | 2 +- src/Handler/CompleteOrderHandler.php | 14 +-- 4 files changed, 102 insertions(+), 20 deletions(-) diff --git a/spec/Handler/CompleteOrderHandlerSpec.php b/spec/Handler/CompleteOrderHandlerSpec.php index 844208517..4a0f15b62 100644 --- a/spec/Handler/CompleteOrderHandlerSpec.php +++ b/spec/Handler/CompleteOrderHandlerSpec.php @@ -9,28 +9,66 @@ use SM\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\CustomerInterface; use Sylius\Component\Core\Model\OrderInterface; +use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\Component\Core\OrderCheckoutTransitions; +use Sylius\Component\Core\Repository\CustomerRepositoryInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; +use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; -use Sylius\ShopApiPlugin\Provider\CustomerProviderInterface; +use Sylius\ShopApiPlugin\Exception\NotLoggedInException; +use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; final class CompleteOrderHandlerSpec extends ObjectBehavior { - function let(OrderRepositoryInterface $orderRepository, CustomerProviderInterface $customerProvider, StateMachineFactoryInterface $stateMachineFactory): void - { - $this->beConstructedWith($orderRepository, $customerProvider, $stateMachineFactory); + function let( + OrderRepositoryInterface $orderRepository, + CustomerRepositoryInterface $customerRepository, + FactoryInterface $customerFactory, + LoggedInUserProviderInterface $loggedInUserProvider, + StateMachineFactoryInterface $stateMachineFactory + ): void { + $this->beConstructedWith($orderRepository, $customerRepository, $customerFactory, $loggedInUserProvider, $stateMachineFactory); + } + + function it_handles_order_completion_for_guest_checkout( + CustomerInterface $customer, + CustomerRepositoryInterface $customerRepository, + FactoryInterface $customerFactory, + OrderInterface $order, + OrderRepositoryInterface $orderRepository, + StateMachineFactoryInterface $stateMachineFactory, + StateMachineInterface $stateMachine + ): void { + $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); + + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn(null); + $customerFactory->createNew()->willReturn($customer); + + $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); + $stateMachine->can('complete')->willReturn(true); + + $order->setNotes(null)->shouldBeCalled(); + $order->setCustomer($customer)->shouldBeCalled(); + $stateMachine->apply('complete')->shouldBeCalled(); + + $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com')); } function it_handles_order_completion_for_existing_customer( CustomerInterface $customer, - CustomerProviderInterface $customerProvider, + CustomerRepositoryInterface $customerRepository, + LoggedInUserProviderInterface $loggedInUserProvider, + ShopUserInterface $shopUser, OrderInterface $order, OrderRepositoryInterface $orderRepository, StateMachineFactoryInterface $stateMachineFactory, StateMachineInterface $stateMachine ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - $customerProvider->provide('example@customer.com')->willReturn($customer); + + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); + $shopUser->getCustomer()->willReturn($customer); + $loggedInUserProvider->provide()->willReturn($shopUser); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can('complete')->willReturn(true); @@ -44,14 +82,19 @@ function it_handles_order_completion_for_existing_customer( function it_handles_order_completion_with_notes( CustomerInterface $customer, - CustomerProviderInterface $customerProvider, + CustomerRepositoryInterface $customerRepository, + LoggedInUserProviderInterface $loggedInUserProvider, + ShopUserInterface $shopUser, OrderInterface $order, OrderRepositoryInterface $orderRepository, StateMachineFactoryInterface $stateMachineFactory, StateMachineInterface $stateMachine ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - $customerProvider->provide('example@customer.com')->willReturn($customer); + + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); + $shopUser->getCustomer()->willReturn($customer); + $loggedInUserProvider->provide()->willReturn($shopUser); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can('complete')->willReturn(true); @@ -63,11 +106,42 @@ function it_handles_order_completion_with_notes( $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')); } - function it_throws_an_exception_if_order_does_not_exist(OrderRepositoryInterface $orderRepository): void - { + function it_throws_an_exception_if_order_does_not_exist( + OrderRepositoryInterface $orderRepository + ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn(null); - $this->shouldThrow(\InvalidArgumentException::class)->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]); + $this->shouldThrow(\InvalidArgumentException::class) + ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]) + ; + } + + function it_throws_an_exception_if_the_user_is_not_logged_in( + CustomerInterface $customer, + CustomerRepositoryInterface $customerRepository, + LoggedInUserProviderInterface $loggedInUserProvider, + CustomerInterface $loggedInCustomer, + ShopUserInterface $shopUser, + OrderInterface $order, + OrderRepositoryInterface $orderRepository, + StateMachineFactoryInterface $stateMachineFactory, + StateMachineInterface $stateMachine + ): void { + $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); + + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); + $shopUser->getCustomer()->willReturn($loggedInCustomer); + $loggedInUserProvider->provide()->willReturn($shopUser); + + $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); + $stateMachine->can('complete')->willReturn(true); + + $order->setCustomer($customer)->shouldNotBeCalled(); + $stateMachine->apply('complete')->shouldNotBeCalled(); + + $this->shouldThrow(NotLoggedInException::class) + ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')]) + ; } function it_throws_an_exception_if_order_cannot_be_addressed( diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index c3d7a9845..e9fc352e1 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -35,6 +35,7 @@ public function __construct(ViewHandlerInterface $viewHandler, CommandBus $bus, public function __invoke(Request $request): Response { $email = $this->provideUserEmail($request); + try { $this->bus->handle( new CompleteOrder( @@ -43,8 +44,13 @@ public function __invoke(Request $request): Response $request->request->get('notes') ) ); - }catch (NotLoggedInException $notLoggedInException) { - return $this->viewHandler->handle(View::create("You need to be logged in with the same user that wants to complete the order", Response::HTTP_UNAUTHORIZED)); + } catch (NotLoggedInException $notLoggedInException) { + return $this->viewHandler->handle( + View::create( + 'You need to be logged in with the same user that wants to complete the order', + Response::HTTP_UNAUTHORIZED + ) + ); } return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); diff --git a/src/Exception/NotLoggedInException.php b/src/Exception/NotLoggedInException.php index 6d6e318cb..a2d857419 100644 --- a/src/Exception/NotLoggedInException.php +++ b/src/Exception/NotLoggedInException.php @@ -1,4 +1,5 @@ orderRepository = $orderRepository; @@ -58,13 +58,15 @@ public function handle(CompleteOrder $completeOrder) Assert::true($stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE), sprintf('Order with %s token cannot be completed.', $completeOrder->orderToken())); + $customer = $this->getCustomer($completeOrder->email()); $order->setNotes($completeOrder->notes()); - $order->setCustomer($this->getCustomer($completeOrder->email())); + $order->setCustomer($customer); $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_COMPLETE); } - private function getCustomer(string $emailAddress): CustomerInterface { + private function getCustomer(string $emailAddress): CustomerInterface + { /** @var CustomerInterface|null $customer */ $customer = $this->customerRepository->findOneBy(['email' => $emailAddress]); @@ -78,7 +80,7 @@ private function getCustomer(string $emailAddress): CustomerInterface { // If the customer does exist the user has to be logged in with this customer. Otherwise the user is not authorized to complete the checkout $loggedInUser = $this->loggedInUserProvider->provide(); - if($loggedInUser === null || $loggedInUser->getCustomer() !== $customer) { + if ($loggedInUser === null || $loggedInUser->getCustomer() !== $customer) { throw new NotLoggedInException(); } From 836982ce38456d9a24d940678ea40ef18fd1ee2d Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 16:41:19 +0100 Subject: [PATCH 03/14] Removed redundant comparision --- src/Controller/Checkout/CompleteOrderAction.php | 8 ++++++++ src/Handler/CompleteOrderHandler.php | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index e9fc352e1..9c8262dfa 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; final class CompleteOrderAction { @@ -51,6 +52,13 @@ public function __invoke(Request $request): Response Response::HTTP_UNAUTHORIZED ) ); + } catch (TokenNotFoundException $notLoggedInException) { + return $this->viewHandler->handle( + View::create( + 'You need to be logged in with the same user that wants to complete the order', + Response::HTTP_UNAUTHORIZED + ) + ); } return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index 4342c862d..d2fc76f17 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -72,6 +72,7 @@ private function getCustomer(string $emailAddress): CustomerInterface // If the customer does not exist then it's normal checkout if ($customer === null) { + /** @var CustomerInterface $customer */ $customer = $this->customerFactory->createNew(); $customer->setEmail($emailAddress); @@ -80,7 +81,8 @@ private function getCustomer(string $emailAddress): CustomerInterface // If the customer does exist the user has to be logged in with this customer. Otherwise the user is not authorized to complete the checkout $loggedInUser = $this->loggedInUserProvider->provide(); - if ($loggedInUser === null || $loggedInUser->getCustomer() !== $customer) { + + if ($loggedInUser->getCustomer() !== $customer) { throw new NotLoggedInException(); } From 069e714532e30be02db62ca5a030c4b7b88a698e Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 16:48:13 +0100 Subject: [PATCH 04/14] Refactored to use the user provider --- doc/swagger.yml | 2 ++ .../Checkout/CompleteOrderAction.php | 26 +++++++++---------- .../config/services/actions/checkout.xml | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/swagger.yml b/doc/swagger.yml index c41e14c62..676e5346f 100644 --- a/doc/swagger.yml +++ b/doc/swagger.yml @@ -543,6 +543,8 @@ paths: description: "Invalid input, validation failed." schema: $ref: "#/definitions/GeneralError" + 403: + description: "Not logged in or wrong email" /taxon-products-by-slug/{slug}: get: tags: diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index 9c8262dfa..ddd95292f 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -7,12 +7,11 @@ use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use League\Tactician\CommandBus; -use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; use Sylius\ShopApiPlugin\Exception\NotLoggedInException; +use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; final class CompleteOrderAction @@ -23,14 +22,17 @@ final class CompleteOrderAction /** @var CommandBus */ private $bus; - /** @var TokenStorageInterface */ - private $tokenStorage; + /** @var LoggedInUserProviderInterface */ + private $loggedInUserProvider; - public function __construct(ViewHandlerInterface $viewHandler, CommandBus $bus, TokenStorageInterface $tokenStorage) - { + public function __construct( + ViewHandlerInterface $viewHandler, + CommandBus $bus, + LoggedInUserProviderInterface $loggedInUserProvider + ) { $this->viewHandler = $viewHandler; $this->bus = $bus; - $this->tokenStorage = $tokenStorage; + $this->loggedInUserProvider = $loggedInUserProvider; } public function __invoke(Request $request): Response @@ -66,12 +68,10 @@ public function __invoke(Request $request): Response private function provideUserEmail(Request $request): string { - $user = $this->tokenStorage->getToken()->getUser(); - - if ($user instanceof ShopUserInterface) { - return $user->getCustomer()->getEmail(); + try { + return $this->loggedInUserProvider->provide()->getEmail(); + } catch (TokenNotFoundException $tokenNotFoundException) { + return $request->request->get('email'); } - - return $request->request->get('email'); } } diff --git a/src/Resources/config/services/actions/checkout.xml b/src/Resources/config/services/actions/checkout.xml index b94d9bfc7..e1d31ef66 100644 --- a/src/Resources/config/services/actions/checkout.xml +++ b/src/Resources/config/services/actions/checkout.xml @@ -43,7 +43,7 @@ > - + From 2d6deaa50eb8c973e084d0d1057b2086c3016d50 Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 16:57:17 +0100 Subject: [PATCH 05/14] Renaming the exception --- spec/Handler/CompleteOrderHandlerSpec.php | 4 ++-- src/Controller/Checkout/CompleteOrderAction.php | 9 +++------ .../{NotLoggedInException.php => WrongUserException.php} | 2 +- src/Handler/CompleteOrderHandler.php | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) rename src/Exception/{NotLoggedInException.php => WrongUserException.php} (68%) diff --git a/spec/Handler/CompleteOrderHandlerSpec.php b/spec/Handler/CompleteOrderHandlerSpec.php index 4a0f15b62..c957c2a05 100644 --- a/spec/Handler/CompleteOrderHandlerSpec.php +++ b/spec/Handler/CompleteOrderHandlerSpec.php @@ -15,7 +15,7 @@ use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; -use Sylius\ShopApiPlugin\Exception\NotLoggedInException; +use Sylius\ShopApiPlugin\Exception\WrongUserException; use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; final class CompleteOrderHandlerSpec extends ObjectBehavior @@ -139,7 +139,7 @@ function it_throws_an_exception_if_the_user_is_not_logged_in( $order->setCustomer($customer)->shouldNotBeCalled(); $stateMachine->apply('complete')->shouldNotBeCalled(); - $this->shouldThrow(NotLoggedInException::class) + $this->shouldThrow(WrongUserException::class) ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')]) ; } diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index ddd95292f..5366d570a 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -8,7 +8,7 @@ use FOS\RestBundle\View\ViewHandlerInterface; use League\Tactician\CommandBus; use Sylius\ShopApiPlugin\Command\CompleteOrder; -use Sylius\ShopApiPlugin\Exception\NotLoggedInException; +use Sylius\ShopApiPlugin\Exception\WrongUserException; use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -47,7 +47,7 @@ public function __invoke(Request $request): Response $request->request->get('notes') ) ); - } catch (NotLoggedInException $notLoggedInException) { + } catch (WrongUserException $notLoggedInException) { return $this->viewHandler->handle( View::create( 'You need to be logged in with the same user that wants to complete the order', @@ -56,10 +56,7 @@ public function __invoke(Request $request): Response ); } catch (TokenNotFoundException $notLoggedInException) { return $this->viewHandler->handle( - View::create( - 'You need to be logged in with the same user that wants to complete the order', - Response::HTTP_UNAUTHORIZED - ) + View::create('You need to be logged in', Response::HTTP_UNAUTHORIZED) ); } diff --git a/src/Exception/NotLoggedInException.php b/src/Exception/WrongUserException.php similarity index 68% rename from src/Exception/NotLoggedInException.php rename to src/Exception/WrongUserException.php index a2d857419..6522443ed 100644 --- a/src/Exception/NotLoggedInException.php +++ b/src/Exception/WrongUserException.php @@ -6,6 +6,6 @@ use Exception; -class NotLoggedInException extends Exception +class WrongUserException extends Exception { } diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index d2fc76f17..638d74301 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -12,7 +12,7 @@ use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; -use Sylius\ShopApiPlugin\Exception\NotLoggedInException; +use Sylius\ShopApiPlugin\Exception\WrongUserException; use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; use Webmozart\Assert\Assert; @@ -83,7 +83,7 @@ private function getCustomer(string $emailAddress): CustomerInterface $loggedInUser = $this->loggedInUserProvider->provide(); if ($loggedInUser->getCustomer() !== $customer) { - throw new NotLoggedInException(); + throw new WrongUserException(); } return $customer; From 98f9981eee34b5bf678420fd4cbbf14b394f0ac9 Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 17:14:04 +0100 Subject: [PATCH 06/14] Fixed tests --- tests/Controller/CheckoutCompleteOrderApiTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Controller/CheckoutCompleteOrderApiTest.php b/tests/Controller/CheckoutCompleteOrderApiTest.php index cfad66497..abdd0aef0 100644 --- a/tests/Controller/CheckoutCompleteOrderApiTest.php +++ b/tests/Controller/CheckoutCompleteOrderApiTest.php @@ -181,7 +181,7 @@ public function it_allows_to_complete_checkout_without_email_for_logged_in_custo */ public function it_does_not_allow_to_complete_order_in_non_existent_channel() { - $this->loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml']); + $this->loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml', 'customer.yml']); $token = 'SDAOSLEFNWU35H3QLI5325'; @@ -218,7 +218,7 @@ public function it_does_not_allow_to_complete_order_in_non_existent_channel() "email": "example@cusomer.com" } EOT; - $this->client->request('PUT', sprintf('/shop-api/SPACE_KLINGON/checkout/%s/complete', $token), [], [], [ + $this->client->request('PUT', sprintf('/shop-api/WEB_GB/checkout/%s/complete', $token), [], [], [ 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json', ], $data); From ef6dc5193491dbbb8d65061cb408ec351471978b Mon Sep 17 00:00:00 2001 From: mamazu Date: Fri, 23 Nov 2018 17:36:57 +0100 Subject: [PATCH 07/14] Fixed routes in PHPUnit Test --- tests/Controller/CheckoutCompleteOrderApiTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Controller/CheckoutCompleteOrderApiTest.php b/tests/Controller/CheckoutCompleteOrderApiTest.php index abdd0aef0..85497ec0e 100644 --- a/tests/Controller/CheckoutCompleteOrderApiTest.php +++ b/tests/Controller/CheckoutCompleteOrderApiTest.php @@ -218,7 +218,7 @@ public function it_does_not_allow_to_complete_order_in_non_existent_channel() "email": "example@cusomer.com" } EOT; - $this->client->request('PUT', sprintf('/shop-api/WEB_GB/checkout/%s/complete', $token), [], [], [ + $this->client->request('PUT', sprintf('/shop-api/SPACE_KLINGON/checkout/%s/complete', $token), [], [], [ 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json', ], $data); @@ -262,13 +262,14 @@ public function it_disallows_users_to_complete_checkout_for_someone_else() )); $bus->handle(new ChooseShippingMethod($token, 0, 'DHL')); $bus->handle(new ChoosePaymentMethod($token, 0, 'PBC')); + $data = <<client->request('PUT', sprintf('/shop-api/checkout/%s/complete', $token), [], [], [ + $this->client->request('PUT', sprintf('/shop-api/WEB_GB/checkout/%s/complete', $token), [], [], [ 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json', ], $data); From 2fcec7dc6f50288f636065abe24224dc1dcd648b Mon Sep 17 00:00:00 2001 From: mamazu Date: Wed, 28 Nov 2018 21:11:55 +0100 Subject: [PATCH 08/14] Changed the code to match the requirements --- src/Handler/CompleteOrderHandler.php | 33 +++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index 638d74301..017db53b2 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -14,6 +14,7 @@ use Sylius\ShopApiPlugin\Command\CompleteOrder; use Sylius\ShopApiPlugin\Exception\WrongUserException; use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; use Webmozart\Assert\Assert; final class CompleteOrderHandler @@ -67,25 +68,31 @@ public function handle(CompleteOrder $completeOrder) private function getCustomer(string $emailAddress): CustomerInterface { - /** @var CustomerInterface|null $customer */ - $customer = $this->customerRepository->findOneBy(['email' => $emailAddress]); + try { + $loggedInUser = $this->loggedInUserProvider->provide(); + + if ($emailAddress !== '') { + throw new \InvalidArgumentException('Can not have a logged in user and an email address'); + } - // If the customer does not exist then it's normal checkout - if ($customer === null) { /** @var CustomerInterface $customer */ - $customer = $this->customerFactory->createNew(); - $customer->setEmail($emailAddress); + $customer = $loggedInUser->getCustomer(); return $customer; - } + } catch (TokenNotFoundException $notLoggedIn) { + /** @var CustomerInterface|null $customer */ + $customer = $this->customerRepository->findOneBy(['email' => $emailAddress]); - // If the customer does exist the user has to be logged in with this customer. Otherwise the user is not authorized to complete the checkout - $loggedInUser = $this->loggedInUserProvider->provide(); + // If the customer does not exist then it's normal checkout + if ($customer === null) { + /** @var CustomerInterface $customer */ + $customer = $this->customerFactory->createNew(); + $customer->setEmail($emailAddress); - if ($loggedInUser->getCustomer() !== $customer) { - throw new WrongUserException(); - } + return $customer; + } - return $customer; + throw new WrongUserException('Email is already taken'); + } } } From d1e99a2179ccc1a52b8235259f3fc564378e53b2 Mon Sep 17 00:00:00 2001 From: mamazu Date: Wed, 12 Dec 2018 08:40:26 +0100 Subject: [PATCH 09/14] Fixed phpspec tests --- spec/Handler/CompleteOrderHandlerSpec.php | 46 +++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/spec/Handler/CompleteOrderHandlerSpec.php b/spec/Handler/CompleteOrderHandlerSpec.php index c957c2a05..45edd2570 100644 --- a/spec/Handler/CompleteOrderHandlerSpec.php +++ b/spec/Handler/CompleteOrderHandlerSpec.php @@ -5,6 +5,7 @@ namespace spec\Sylius\ShopApiPlugin\Handler; use PhpSpec\ObjectBehavior; +use Prophecy\Argument; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; use SM\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\CustomerInterface; @@ -17,6 +18,7 @@ use Sylius\ShopApiPlugin\Command\CompleteOrder; use Sylius\ShopApiPlugin\Exception\WrongUserException; use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; final class CompleteOrderHandlerSpec extends ObjectBehavior { @@ -33,6 +35,7 @@ function let( function it_handles_order_completion_for_guest_checkout( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, + LoggedInUserProviderInterface $loggedInUserProvider, FactoryInterface $customerFactory, OrderInterface $order, OrderRepositoryInterface $orderRepository, @@ -43,6 +46,7 @@ function it_handles_order_completion_for_guest_checkout( $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn(null); $customerFactory->createNew()->willReturn($customer); + $loggedInUserProvider->provide()->willThrow(TokenNotFoundException::class); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can('complete')->willReturn(true); @@ -54,7 +58,7 @@ function it_handles_order_completion_for_guest_checkout( $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com')); } - function it_handles_order_completion_for_existing_customer( + function it_throws_an_exception_if_the_email_address_has_already_a_customer( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, LoggedInUserProviderInterface $loggedInUserProvider, @@ -68,16 +72,43 @@ function it_handles_order_completion_for_existing_customer( $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); $shopUser->getCustomer()->willReturn($customer); + $loggedInUserProvider->provide()->willThrow(TokenNotFoundException::class); + + $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); + $stateMachine->can('complete')->willReturn(true); + + $order->setNotes(Argument::any())->shouldNotBeCalled(); + $order->setCustomer(Argument::any())->shouldNotBeCalled(); + $stateMachine->apply(Argument::any())->shouldNotBeCalled(); + + $this->shouldThrow(WrongUserException::class) + ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]); + } + + function it_handles_order_completetion( + CustomerRepositoryInterface $customerRepository, + LoggedInUserProviderInterface $loggedInUserProvider, + CustomerInterface $loggedInCustomer, + ShopUserInterface $shopUser, + OrderInterface $order, + OrderRepositoryInterface $orderRepository, + StateMachineFactoryInterface $stateMachineFactory, + StateMachineInterface $stateMachine + ): void { + $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); + + $customerRepository->findOneBy(Argument::any())->shouldNotBeCalled(); + $shopUser->getCustomer()->willReturn($loggedInCustomer); $loggedInUserProvider->provide()->willReturn($shopUser); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can('complete')->willReturn(true); + $order->setCustomer($loggedInCustomer)->shouldBeCalled(); $order->setNotes(null)->shouldBeCalled(); - $order->setCustomer($customer)->shouldBeCalled(); $stateMachine->apply('complete')->shouldBeCalled(); - $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com')); + $this->handle(new CompleteOrder('ORDERTOKEN', '')); } function it_handles_order_completion_with_notes( @@ -103,7 +134,7 @@ function it_handles_order_completion_with_notes( $order->setCustomer($customer)->shouldBeCalled(); $stateMachine->apply('complete')->shouldBeCalled(); - $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')); + $this->handle(new CompleteOrder('ORDERTOKEN', '', 'Some notes')); } function it_throws_an_exception_if_order_does_not_exist( @@ -116,7 +147,7 @@ function it_throws_an_exception_if_order_does_not_exist( ; } - function it_throws_an_exception_if_the_user_is_not_logged_in( + function it_throws_an_exception_if_the_user_is_logged_in_and_provides_email( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, LoggedInUserProviderInterface $loggedInUserProvider, @@ -129,7 +160,7 @@ function it_throws_an_exception_if_the_user_is_not_logged_in( ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); + $customerRepository->findOneBy(Argument::any())->shouldNotBeCalled(); $shopUser->getCustomer()->willReturn($loggedInCustomer); $loggedInUserProvider->provide()->willReturn($shopUser); @@ -137,9 +168,10 @@ function it_throws_an_exception_if_the_user_is_not_logged_in( $stateMachine->can('complete')->willReturn(true); $order->setCustomer($customer)->shouldNotBeCalled(); + $order->setNotes('Some notes'); $stateMachine->apply('complete')->shouldNotBeCalled(); - $this->shouldThrow(WrongUserException::class) + $this->shouldThrow(\InvalidArgumentException::class) ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')]) ; } From ae7433f15e7dd9213ba6a8223edba9a04df86730 Mon Sep 17 00:00:00 2001 From: mamazu Date: Wed, 12 Dec 2018 17:18:11 +0100 Subject: [PATCH 10/14] Fixed tests for logged in customers --- src/Controller/Checkout/CompleteOrderAction.php | 16 +++------------- src/Handler/CompleteOrderHandler.php | 1 + .../Controller/CheckoutCompleteOrderApiTest.php | 16 ++++------------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index 5366d570a..80a304c9f 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -30,20 +30,18 @@ public function __construct( CommandBus $bus, LoggedInUserProviderInterface $loggedInUserProvider ) { - $this->viewHandler = $viewHandler; - $this->bus = $bus; + $this->viewHandler = $viewHandler; + $this->bus = $bus; $this->loggedInUserProvider = $loggedInUserProvider; } public function __invoke(Request $request): Response { - $email = $this->provideUserEmail($request); - try { $this->bus->handle( new CompleteOrder( $request->attributes->get('token'), - $email, + $request->request->get('email', ''), $request->request->get('notes') ) ); @@ -63,12 +61,4 @@ public function __invoke(Request $request): Response return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); } - private function provideUserEmail(Request $request): string - { - try { - return $this->loggedInUserProvider->provide()->getEmail(); - } catch (TokenNotFoundException $tokenNotFoundException) { - return $request->request->get('email'); - } - } } diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index 017db53b2..0b37ffebb 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -72,6 +72,7 @@ private function getCustomer(string $emailAddress): CustomerInterface $loggedInUser = $this->loggedInUserProvider->provide(); if ($emailAddress !== '') { + throw new \InvalidArgumentException($emailAddress.' has to be empty'); throw new \InvalidArgumentException('Can not have a logged in user and an email address'); } diff --git a/tests/Controller/CheckoutCompleteOrderApiTest.php b/tests/Controller/CheckoutCompleteOrderApiTest.php index 85497ec0e..1687d6145 100644 --- a/tests/Controller/CheckoutCompleteOrderApiTest.php +++ b/tests/Controller/CheckoutCompleteOrderApiTest.php @@ -12,9 +12,12 @@ use Sylius\ShopApiPlugin\Command\PutSimpleItemToCart; use Sylius\ShopApiPlugin\Model\Address; use Symfony\Component\HttpFoundation\Response; +use Tests\Sylius\ShopApiPlugin\Controller\Utils\ShopUserLoginTrait; final class CheckoutCompleteOrderApiTest extends JsonApiTestCase { + use ShopUserLoginTrait; + /** * @test */ @@ -154,19 +157,8 @@ public function it_allows_to_complete_checkout_without_email_for_logged_in_custo $bus->handle(new ChooseShippingMethod($token, 0, 'DHL')); $bus->handle(new ChoosePaymentMethod($token, 0, 'PBC')); - $data = -<<client->request('POST', '/shop-api/login_check', [], [], ['CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'], $data); - - $response = json_decode($this->client->getResponse()->getContent(), true); + $this->logInUser('oliver@queen.com', '123password'); - $this->client->setServerParameter('HTTP_Authorization', sprintf('Bearer %s', $response['token'])); $this->client->request('PUT', sprintf('/shop-api/WEB_GB/checkout/%s/complete', $token), [], [], [ 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json', From 52e4e05a675ccf252d307a47c4987683ad59f357 Mon Sep 17 00:00:00 2001 From: mamazu Date: Wed, 12 Dec 2018 17:55:28 +0100 Subject: [PATCH 11/14] Adding check if a token exists --- src/Provider/LoggedInUserProvider.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Provider/LoggedInUserProvider.php b/src/Provider/LoggedInUserProvider.php index 1493271ea..fd6f2d624 100644 --- a/src/Provider/LoggedInUserProvider.php +++ b/src/Provider/LoggedInUserProvider.php @@ -20,11 +20,16 @@ public function __construct(TokenStorageInterface $tokenStorage) public function provide(): ShopUserInterface { - /** @var ShopUserInterface $user */ - $user = $this->tokenStorage->getToken()->getUser(); + $token = $this->tokenStorage->getToken(); + if($token === null) { + throw new TokenNotFoundException('No token found'); + } + + /** @var ShopUserInterface|null $user */ + $user = $token->getUser(); if (!$user instanceof ShopUserInterface) { - throw new TokenNotFoundException(); + throw new TokenNotFoundException('No logged in user'); } return $user; From 0408b78d33178380387e11d9947caaa418af47c5 Mon Sep 17 00:00:00 2001 From: mamazu Date: Wed, 12 Dec 2018 17:58:59 +0100 Subject: [PATCH 12/14] Codestyle --- src/Controller/Checkout/CompleteOrderAction.php | 5 ++--- src/Handler/CompleteOrderHandler.php | 3 ++- src/Provider/LoggedInUserProvider.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index 80a304c9f..a427f7e66 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -30,8 +30,8 @@ public function __construct( CommandBus $bus, LoggedInUserProviderInterface $loggedInUserProvider ) { - $this->viewHandler = $viewHandler; - $this->bus = $bus; + $this->viewHandler = $viewHandler; + $this->bus = $bus; $this->loggedInUserProvider = $loggedInUserProvider; } @@ -60,5 +60,4 @@ public function __invoke(Request $request): Response return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); } - } diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index 0b37ffebb..bd928421c 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -72,7 +72,8 @@ private function getCustomer(string $emailAddress): CustomerInterface $loggedInUser = $this->loggedInUserProvider->provide(); if ($emailAddress !== '') { - throw new \InvalidArgumentException($emailAddress.' has to be empty'); + throw new \InvalidArgumentException($emailAddress . ' has to be empty'); + throw new \InvalidArgumentException('Can not have a logged in user and an email address'); } diff --git a/src/Provider/LoggedInUserProvider.php b/src/Provider/LoggedInUserProvider.php index fd6f2d624..6547879f2 100644 --- a/src/Provider/LoggedInUserProvider.php +++ b/src/Provider/LoggedInUserProvider.php @@ -21,12 +21,12 @@ public function __construct(TokenStorageInterface $tokenStorage) public function provide(): ShopUserInterface { $token = $this->tokenStorage->getToken(); - if($token === null) { + if ($token === null) { throw new TokenNotFoundException('No token found'); } /** @var ShopUserInterface|null $user */ - $user = $token->getUser(); + $user = $token->getUser(); if (!$user instanceof ShopUserInterface) { throw new TokenNotFoundException('No logged in user'); From 2e64316a0db9d6d2d6237ee533d5641982e930ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Chru=C5=9Bciel?= Date: Thu, 3 Jan 2019 14:43:58 +0100 Subject: [PATCH 13/14] Improve logged in shop user provider --- spec/Handler/CompleteOrderHandlerSpec.php | 14 ++-- .../Provider/LoggedInShopUserProviderSpec.php | 65 +++++++++++++++++++ spec/Provider/LoggedInUserProviderSpec.php | 46 ------------- .../Address/AddressExistsValidatorSpec.php | 8 +-- .../AddressBook/CreateAddressAction.php | 6 +- .../AddressBook/RemoveAddressAction.php | 6 +- .../AddressBook/SetDefaultAddressAction.php | 6 +- .../AddressBook/ShowAddressBookAction.php | 6 +- .../AddressBook/UpdateAddressAction.php | 6 +- .../Checkout/CompleteOrderAction.php | 6 +- .../Order/ShowOrderDetailsAction.php | 6 +- src/Controller/Order/ShowOrdersListAction.php | 6 +- src/Handler/CompleteOrderHandler.php | 8 +-- ...vider.php => LoggedInShopUserProvider.php} | 9 ++- ... => LoggedInShopUserProviderInterface.php} | 4 +- src/Resources/config/services/providers.xml | 2 +- .../Address/AddressExistsValidator.php | 6 +- 17 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 spec/Provider/LoggedInShopUserProviderSpec.php delete mode 100644 spec/Provider/LoggedInUserProviderSpec.php rename src/Provider/{LoggedInUserProvider.php => LoggedInShopUserProvider.php} (78%) rename src/Provider/{LoggedInUserProviderInterface.php => LoggedInShopUserProviderInterface.php} (69%) diff --git a/spec/Handler/CompleteOrderHandlerSpec.php b/spec/Handler/CompleteOrderHandlerSpec.php index 45edd2570..d8fe1a3f3 100644 --- a/spec/Handler/CompleteOrderHandlerSpec.php +++ b/spec/Handler/CompleteOrderHandlerSpec.php @@ -17,7 +17,7 @@ use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; use Sylius\ShopApiPlugin\Exception\WrongUserException; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; final class CompleteOrderHandlerSpec extends ObjectBehavior @@ -26,7 +26,7 @@ function let( OrderRepositoryInterface $orderRepository, CustomerRepositoryInterface $customerRepository, FactoryInterface $customerFactory, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, StateMachineFactoryInterface $stateMachineFactory ): void { $this->beConstructedWith($orderRepository, $customerRepository, $customerFactory, $loggedInUserProvider, $stateMachineFactory); @@ -35,7 +35,7 @@ function let( function it_handles_order_completion_for_guest_checkout( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, FactoryInterface $customerFactory, OrderInterface $order, OrderRepositoryInterface $orderRepository, @@ -61,7 +61,7 @@ function it_handles_order_completion_for_guest_checkout( function it_throws_an_exception_if_the_email_address_has_already_a_customer( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, ShopUserInterface $shopUser, OrderInterface $order, OrderRepositoryInterface $orderRepository, @@ -87,7 +87,7 @@ function it_throws_an_exception_if_the_email_address_has_already_a_customer( function it_handles_order_completetion( CustomerRepositoryInterface $customerRepository, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, CustomerInterface $loggedInCustomer, ShopUserInterface $shopUser, OrderInterface $order, @@ -114,7 +114,7 @@ function it_handles_order_completetion( function it_handles_order_completion_with_notes( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, ShopUserInterface $shopUser, OrderInterface $order, OrderRepositoryInterface $orderRepository, @@ -150,7 +150,7 @@ function it_throws_an_exception_if_order_does_not_exist( function it_throws_an_exception_if_the_user_is_logged_in_and_provides_email( CustomerInterface $customer, CustomerRepositoryInterface $customerRepository, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, CustomerInterface $loggedInCustomer, ShopUserInterface $shopUser, OrderInterface $order, diff --git a/spec/Provider/LoggedInShopUserProviderSpec.php b/spec/Provider/LoggedInShopUserProviderSpec.php new file mode 100644 index 000000000..f961b4f79 --- /dev/null +++ b/spec/Provider/LoggedInShopUserProviderSpec.php @@ -0,0 +1,65 @@ +beConstructedWith($tokenStorage); + } + + function it_is_reviewer_subject_provider(): void + { + $this->shouldImplement(LoggedInShopUserProviderInterface::class); + } + + function it_throws_an_error_if_there_is_no_shop_user_logged_in( + TokenStorageInterface $tokenStorage, + TokenInterface $token, + UserInterface $anotherUser + ): void { + $tokenStorage->getToken()->willReturn(null, $token); + $token->getUser()->willReturn(null, $anotherUser); + + $this->shouldThrow(TokenNotFoundException::class)->during('provide'); + $this->shouldThrow(TokenNotFoundException::class)->during('provide'); + $this->shouldThrow(TokenNotFoundException::class)->during('provide'); + } + + function it_returns_the_logged_in_user_if_there_is_one( + TokenStorageInterface $tokenStorage, + TokenInterface $token, + ShopUserInterface $shopUser + ): void { + $token->getUser()->willReturn($shopUser); + $tokenStorage->getToken()->willReturn($token); + + $this->provide()->shouldReturn($shopUser); + } + + function it_checks_if_shop_user_is_logged_in( + TokenStorageInterface $tokenStorage, + TokenInterface $token, + ShopUserInterface $shopUser, + UserInterface $anotherUser + ): void { + $tokenStorage->getToken()->willReturn(null, $token); + $token->getUser()->willReturn(null, $anotherUser, $shopUser); + + $this->check()->shouldReturn(false); + $this->check()->shouldReturn(false); + $this->check()->shouldReturn(false); + $this->check()->shouldReturn(true); + } +} diff --git a/spec/Provider/LoggedInUserProviderSpec.php b/spec/Provider/LoggedInUserProviderSpec.php deleted file mode 100644 index 19d9e9d38..000000000 --- a/spec/Provider/LoggedInUserProviderSpec.php +++ /dev/null @@ -1,46 +0,0 @@ -beConstructedWith($tokenStorage); - } - - function it_is_reviewer_subject_provider(): void - { - $this->shouldImplement(LoggedInUserProviderInterface::class); - } - - function it_throws_an_error_if_there_is_no_user_logged_in( - TokenStorageInterface $tokenStorage, - TokenInterface $token - ): void { - $token->getUser()->shouldBeCalled()->willReturn(null); - $tokenStorage->getToken()->shouldBeCalled()->willReturn($token); - - $this->shouldThrow(TokenNotFoundException::class)->during('provide'); - } - - function it_returns_the_logged_in_user_if_there_is_one( - TokenStorageInterface $tokenStorage, - TokenInterface $token, - ShopUserInterface $shopUser - ): void { - $token->getUser()->shouldBeCalled()->willReturn($shopUser); - $tokenStorage->getToken()->shouldBeCalled()->willReturn($token); - - $this->provide()->shouldReturn($shopUser); - } -} diff --git a/spec/Validator/Address/AddressExistsValidatorSpec.php b/spec/Validator/Address/AddressExistsValidatorSpec.php index 9ca1a2abf..8ef435a51 100644 --- a/spec/Validator/Address/AddressExistsValidatorSpec.php +++ b/spec/Validator/Address/AddressExistsValidatorSpec.php @@ -10,7 +10,7 @@ use Sylius\Component\Core\Model\CustomerInterface; use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\Component\Core\Repository\AddressRepositoryInterface; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Sylius\ShopApiPlugin\Validator\Constraints\AddressExists; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -19,7 +19,7 @@ final class AddressExistsValidatorSpec extends ObjectBehavior function let( ExecutionContextInterface $executionContext, AddressRepositoryInterface $addressRepository, - LoggedInUserProviderInterface $currentUserProvider + LoggedInShopUserProviderInterface $currentUserProvider ): void { $this->beConstructedWith($addressRepository, $currentUserProvider); @@ -30,7 +30,7 @@ function it_does_not_add_constraint_if_address_exists_and_its_owned_by_current_u AddressInterface $address, ShopUserInterface $shopUser, CustomerInterface $customerOwner, - LoggedInUserProviderInterface $currentUserProvider, + LoggedInShopUserProviderInterface $currentUserProvider, AddressRepositoryInterface $addressRepository, ExecutionContextInterface $executionContext ): void { @@ -61,7 +61,7 @@ function it_adds_constraint_if_address_does_not_exits_exists( function it_adds_constraint_if_current_user_is_not_address_owner( AddressInterface $address, AddressRepositoryInterface $addressRepository, - LoggedInUserProviderInterface $currentUserProvider, + LoggedInShopUserProviderInterface $currentUserProvider, ShopUserInterface $shopUser, CustomerInterface $customerOwner, ExecutionContextInterface $executionContext diff --git a/src/Controller/AddressBook/CreateAddressAction.php b/src/Controller/AddressBook/CreateAddressAction.php index f0c52d15c..b3273d6b1 100644 --- a/src/Controller/AddressBook/CreateAddressAction.php +++ b/src/Controller/AddressBook/CreateAddressAction.php @@ -14,7 +14,7 @@ use Sylius\ShopApiPlugin\Factory\AddressBookViewFactoryInterface; use Sylius\ShopApiPlugin\Factory\ValidationErrorViewFactoryInterface; use Sylius\ShopApiPlugin\Model\Address; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Sylius\ShopApiPlugin\View\AddressBookView; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -35,7 +35,7 @@ final class CreateAddressAction /** @var ValidationErrorViewFactoryInterface */ private $validationErrorViewFactory; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; /** @var AddressBookViewFactoryInterface */ @@ -51,7 +51,7 @@ public function __construct( ValidationErrorViewFactoryInterface $validationErrorViewFactory, AddressBookViewFactoryInterface $addressViewFactory, AddressRepositoryInterface $addressRepository, - LoggedInUserProviderInterface $loggedInUserProvider + LoggedInShopUserProviderInterface $loggedInUserProvider ) { $this->viewHandler = $viewHandler; $this->bus = $bus; diff --git a/src/Controller/AddressBook/RemoveAddressAction.php b/src/Controller/AddressBook/RemoveAddressAction.php index f8b68b83a..141c4d147 100644 --- a/src/Controller/AddressBook/RemoveAddressAction.php +++ b/src/Controller/AddressBook/RemoveAddressAction.php @@ -10,7 +10,7 @@ use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\ShopApiPlugin\Command\RemoveAddress; use Sylius\ShopApiPlugin\Factory\ValidationErrorViewFactory; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Sylius\ShopApiPlugin\Request\RemoveAddressRequest; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -31,7 +31,7 @@ final class RemoveAddressAction /** @var CommandBus */ private $bus; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; public function __construct( @@ -39,7 +39,7 @@ public function __construct( ValidatorInterface $validator, ValidationErrorViewFactory $validationErrorViewFactory, CommandBus $bus, - LoggedInUserProviderInterface $loggedInUserProvider + LoggedInShopUserProviderInterface $loggedInUserProvider ) { $this->viewHandler = $viewHandler; $this->validator = $validator; diff --git a/src/Controller/AddressBook/SetDefaultAddressAction.php b/src/Controller/AddressBook/SetDefaultAddressAction.php index d45ac2ae7..59de708cf 100644 --- a/src/Controller/AddressBook/SetDefaultAddressAction.php +++ b/src/Controller/AddressBook/SetDefaultAddressAction.php @@ -10,7 +10,7 @@ use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\ShopApiPlugin\Command\SetDefaultAddress; use Sylius\ShopApiPlugin\Factory\ValidationErrorViewFactoryInterface; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Sylius\ShopApiPlugin\Request\SetDefaultAddressRequest; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -31,7 +31,7 @@ final class SetDefaultAddressAction /** @var ValidationErrorViewFactoryInterface */ private $validationErrorViewFactory; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; public function __construct( @@ -39,7 +39,7 @@ public function __construct( CommandBus $bus, ValidatorInterface $validator, ValidationErrorViewFactoryInterface $validationErrorViewFactory, - LoggedInUserProviderInterface $loggedInUserProvider + LoggedInShopUserProviderInterface $loggedInUserProvider ) { $this->viewHandler = $viewHandler; $this->bus = $bus; diff --git a/src/Controller/AddressBook/ShowAddressBookAction.php b/src/Controller/AddressBook/ShowAddressBookAction.php index ea4568527..2396b424a 100644 --- a/src/Controller/AddressBook/ShowAddressBookAction.php +++ b/src/Controller/AddressBook/ShowAddressBookAction.php @@ -10,7 +10,7 @@ use Sylius\Component\Core\Model\Customer; use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\ShopApiPlugin\Factory\AddressBookViewFactoryInterface; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; @@ -19,7 +19,7 @@ final class ShowAddressBookAction /** @var ViewHandlerInterface */ private $viewHandler; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; /** @var AddressBookViewFactoryInterface */ @@ -27,7 +27,7 @@ final class ShowAddressBookAction public function __construct( ViewHandlerInterface $viewHandler, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, AddressBookViewFactoryInterface $addressBookViewFactory ) { $this->viewHandler = $viewHandler; diff --git a/src/Controller/AddressBook/UpdateAddressAction.php b/src/Controller/AddressBook/UpdateAddressAction.php index 46c181e87..a5eea9a2f 100644 --- a/src/Controller/AddressBook/UpdateAddressAction.php +++ b/src/Controller/AddressBook/UpdateAddressAction.php @@ -14,7 +14,7 @@ use Sylius\ShopApiPlugin\Factory\AddressBookViewFactoryInterface; use Sylius\ShopApiPlugin\Factory\ValidationErrorViewFactoryInterface; use Sylius\ShopApiPlugin\Model\Address; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; @@ -40,7 +40,7 @@ final class UpdateAddressAction /** @var AddressRepositoryInterface */ private $addressRepository; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; public function __construct( @@ -50,7 +50,7 @@ public function __construct( ValidationErrorViewFactoryInterface $validationErrorViewFactory, AddressBookViewFactoryInterface $addressViewFactory, AddressRepositoryInterface $addressRepository, - LoggedInUserProviderInterface $loggedInUserProvider + LoggedInShopUserProviderInterface $loggedInUserProvider ) { $this->viewHandler = $viewHandler; $this->validator = $validator; diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index a427f7e66..1008e89f4 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -9,7 +9,7 @@ use League\Tactician\CommandBus; use Sylius\ShopApiPlugin\Command\CompleteOrder; use Sylius\ShopApiPlugin\Exception\WrongUserException; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; @@ -22,13 +22,13 @@ final class CompleteOrderAction /** @var CommandBus */ private $bus; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; public function __construct( ViewHandlerInterface $viewHandler, CommandBus $bus, - LoggedInUserProviderInterface $loggedInUserProvider + LoggedInShopUserProviderInterface $loggedInUserProvider ) { $this->viewHandler = $viewHandler; $this->bus = $bus; diff --git a/src/Controller/Order/ShowOrderDetailsAction.php b/src/Controller/Order/ShowOrderDetailsAction.php index 05c96e0bc..29ada0cae 100644 --- a/src/Controller/Order/ShowOrderDetailsAction.php +++ b/src/Controller/Order/ShowOrderDetailsAction.php @@ -7,8 +7,8 @@ use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Sylius\Component\Core\Model\ShopUserInterface; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; use Sylius\ShopApiPlugin\ViewRepository\Order\PlacedOrderViewRepositoryInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -19,7 +19,7 @@ final class ShowOrderDetailsAction /** @var ViewHandlerInterface */ private $viewHandler; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; /** @var PlacedOrderViewRepositoryInterface */ @@ -27,7 +27,7 @@ final class ShowOrderDetailsAction public function __construct( ViewHandlerInterface $viewHandler, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, PlacedOrderViewRepositoryInterface $placedOrderQuery ) { $this->viewHandler = $viewHandler; diff --git a/src/Controller/Order/ShowOrdersListAction.php b/src/Controller/Order/ShowOrdersListAction.php index 3ddb277f5..358ecf529 100644 --- a/src/Controller/Order/ShowOrdersListAction.php +++ b/src/Controller/Order/ShowOrdersListAction.php @@ -7,7 +7,7 @@ use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Sylius\Component\Core\Model\ShopUserInterface; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Sylius\ShopApiPlugin\ViewRepository\Order\PlacedOrderViewRepositoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -18,7 +18,7 @@ final class ShowOrdersListAction /** @var ViewHandlerInterface */ private $viewHandler; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; /** @var PlacedOrderViewRepositoryInterface */ @@ -26,7 +26,7 @@ final class ShowOrdersListAction public function __construct( ViewHandlerInterface $viewHandler, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, PlacedOrderViewRepositoryInterface $placedOrderQuery ) { $this->viewHandler = $viewHandler; diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index bd928421c..5c22fe646 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -13,7 +13,7 @@ use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; use Sylius\ShopApiPlugin\Exception\WrongUserException; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; use Webmozart\Assert\Assert; @@ -31,14 +31,14 @@ final class CompleteOrderHandler /** @var FactoryInterface */ private $customerFactory; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; public function __construct( OrderRepositoryInterface $orderRepository, CustomerRepositoryInterface $customerRepository, FactoryInterface $customerFactory, - LoggedInUserProviderInterface $loggedInUserProvider, + LoggedInShopUserProviderInterface $loggedInUserProvider, StateMachineFactory $stateMachineFactory ) { $this->orderRepository = $orderRepository; @@ -48,7 +48,7 @@ public function __construct( $this->loggedInUserProvider = $loggedInUserProvider; } - public function handle(CompleteOrder $completeOrder) + public function handle(CompleteOrder $completeOrder): void { /** @var OrderInterface $order */ $order = $this->orderRepository->findOneBy(['tokenValue' => $completeOrder->orderToken()]); diff --git a/src/Provider/LoggedInUserProvider.php b/src/Provider/LoggedInShopUserProvider.php similarity index 78% rename from src/Provider/LoggedInUserProvider.php rename to src/Provider/LoggedInShopUserProvider.php index 6547879f2..ba0d5a2ef 100644 --- a/src/Provider/LoggedInUserProvider.php +++ b/src/Provider/LoggedInShopUserProvider.php @@ -8,7 +8,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\TokenNotFoundException; -final class LoggedInUserProvider implements LoggedInUserProviderInterface +final class LoggedInShopUserProvider implements LoggedInShopUserProviderInterface { /** @var TokenStorageInterface */ private $tokenStorage; @@ -34,4 +34,11 @@ public function provide(): ShopUserInterface return $user; } + + public function check(): bool + { + $token = $this->tokenStorage->getToken(); + + return $token !== null && $token->getUser() instanceof ShopUserInterface; + } } diff --git a/src/Provider/LoggedInUserProviderInterface.php b/src/Provider/LoggedInShopUserProviderInterface.php similarity index 69% rename from src/Provider/LoggedInUserProviderInterface.php rename to src/Provider/LoggedInShopUserProviderInterface.php index f16723abf..2a1800fac 100644 --- a/src/Provider/LoggedInUserProviderInterface.php +++ b/src/Provider/LoggedInShopUserProviderInterface.php @@ -6,7 +6,9 @@ use Sylius\Component\Core\Model\ShopUserInterface; -interface LoggedInUserProviderInterface +interface LoggedInShopUserProviderInterface { public function provide(): ShopUserInterface; + + public function check(): bool; } diff --git a/src/Resources/config/services/providers.xml b/src/Resources/config/services/providers.xml index 0476c3120..759ffad84 100644 --- a/src/Resources/config/services/providers.xml +++ b/src/Resources/config/services/providers.xml @@ -16,7 +16,7 @@ + class="Sylius\ShopApiPlugin\Provider\LoggedInShopUserProvider"> diff --git a/src/Validator/Address/AddressExistsValidator.php b/src/Validator/Address/AddressExistsValidator.php index 31358beb2..6bd8b8653 100644 --- a/src/Validator/Address/AddressExistsValidator.php +++ b/src/Validator/Address/AddressExistsValidator.php @@ -6,7 +6,7 @@ use Sylius\Component\Core\Model\AddressInterface; use Sylius\Component\Core\Repository\AddressRepositoryInterface; -use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; +use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Sylius\ShopApiPlugin\Validator\Constraints\AddressExists; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -16,12 +16,12 @@ final class AddressExistsValidator extends ConstraintValidator /** @var AddressRepositoryInterface */ private $addressRepository; - /** @var LoggedInUserProviderInterface */ + /** @var LoggedInShopUserProviderInterface */ private $loggedInUserProvider; public function __construct( AddressRepositoryInterface $addressRepository, - LoggedInUserProviderInterface $loggedInUserProvider + LoggedInShopUserProviderInterface $loggedInUserProvider ) { $this->addressRepository = $addressRepository; $this->loggedInUserProvider = $loggedInUserProvider; From a8e8762830234ef6cbd44765a9366126c8b12fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Chru=C5=9Bciel?= Date: Thu, 3 Jan 2019 15:20:35 +0100 Subject: [PATCH 14/14] Provide logged in safe customer provider implementation --- spec/Handler/CompleteOrderHandlerSpec.php | 130 +++--------------- spec/Provider/CustomerProviderSpec.php | 45 ------ .../Provider/LoggedInShopUserProviderSpec.php | 8 +- .../ShopUserAwareCustomerProviderSpec.php | 102 ++++++++++++++ .../Checkout/CompleteOrderAction.php | 11 +- .../Order/ShowOrderDetailsAction.php | 2 +- src/Exception/WrongUserException.php | 4 +- src/Handler/CompleteOrderHandler.php | 59 +------- src/Provider/CustomerProvider.php | 40 ------ src/Provider/LoggedInShopUserProvider.php | 2 +- .../LoggedInShopUserProviderInterface.php | 2 +- .../ShopUserAwareCustomerProvider.php | 67 +++++++++ .../config/services/handler/cart.xml | 4 +- src/Resources/config/services/providers.xml | 3 +- .../CheckoutCompleteOrderApiTest.php | 53 ++++++- 15 files changed, 261 insertions(+), 271 deletions(-) delete mode 100644 spec/Provider/CustomerProviderSpec.php create mode 100644 spec/Provider/ShopUserAwareCustomerProviderSpec.php delete mode 100644 src/Provider/CustomerProvider.php create mode 100644 src/Provider/ShopUserAwareCustomerProvider.php diff --git a/spec/Handler/CompleteOrderHandlerSpec.php b/spec/Handler/CompleteOrderHandlerSpec.php index d8fe1a3f3..d793c2428 100644 --- a/spec/Handler/CompleteOrderHandlerSpec.php +++ b/spec/Handler/CompleteOrderHandlerSpec.php @@ -5,38 +5,28 @@ namespace spec\Sylius\ShopApiPlugin\Handler; use PhpSpec\ObjectBehavior; -use Prophecy\Argument; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; use SM\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\CustomerInterface; use Sylius\Component\Core\Model\OrderInterface; -use Sylius\Component\Core\Model\ShopUserInterface; use Sylius\Component\Core\OrderCheckoutTransitions; -use Sylius\Component\Core\Repository\CustomerRepositoryInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; -use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; -use Sylius\ShopApiPlugin\Exception\WrongUserException; -use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; -use Symfony\Component\Security\Core\Exception\TokenNotFoundException; +use Sylius\ShopApiPlugin\Provider\CustomerProviderInterface; final class CompleteOrderHandlerSpec extends ObjectBehavior { function let( OrderRepositoryInterface $orderRepository, - CustomerRepositoryInterface $customerRepository, - FactoryInterface $customerFactory, - LoggedInShopUserProviderInterface $loggedInUserProvider, + CustomerProviderInterface $customerProvider, StateMachineFactoryInterface $stateMachineFactory ): void { - $this->beConstructedWith($orderRepository, $customerRepository, $customerFactory, $loggedInUserProvider, $stateMachineFactory); + $this->beConstructedWith($orderRepository, $customerProvider, $stateMachineFactory); } - function it_handles_order_completion_for_guest_checkout( + function it_handles_order_completion( CustomerInterface $customer, - CustomerRepositoryInterface $customerRepository, - LoggedInShopUserProviderInterface $loggedInUserProvider, - FactoryInterface $customerFactory, + CustomerProviderInterface $customerProvider, OrderInterface $order, OrderRepositoryInterface $orderRepository, StateMachineFactoryInterface $stateMachineFactory, @@ -44,9 +34,7 @@ function it_handles_order_completion_for_guest_checkout( ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn(null); - $customerFactory->createNew()->willReturn($customer); - $loggedInUserProvider->provide()->willThrow(TokenNotFoundException::class); + $customerProvider->provide('example@customer.com')->willReturn($customer); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can('complete')->willReturn(true); @@ -58,64 +46,9 @@ function it_handles_order_completion_for_guest_checkout( $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com')); } - function it_throws_an_exception_if_the_email_address_has_already_a_customer( - CustomerInterface $customer, - CustomerRepositoryInterface $customerRepository, - LoggedInShopUserProviderInterface $loggedInUserProvider, - ShopUserInterface $shopUser, - OrderInterface $order, - OrderRepositoryInterface $orderRepository, - StateMachineFactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine - ): void { - $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - - $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); - $shopUser->getCustomer()->willReturn($customer); - $loggedInUserProvider->provide()->willThrow(TokenNotFoundException::class); - - $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->can('complete')->willReturn(true); - - $order->setNotes(Argument::any())->shouldNotBeCalled(); - $order->setCustomer(Argument::any())->shouldNotBeCalled(); - $stateMachine->apply(Argument::any())->shouldNotBeCalled(); - - $this->shouldThrow(WrongUserException::class) - ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]); - } - - function it_handles_order_completetion( - CustomerRepositoryInterface $customerRepository, - LoggedInShopUserProviderInterface $loggedInUserProvider, - CustomerInterface $loggedInCustomer, - ShopUserInterface $shopUser, - OrderInterface $order, - OrderRepositoryInterface $orderRepository, - StateMachineFactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine - ): void { - $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - - $customerRepository->findOneBy(Argument::any())->shouldNotBeCalled(); - $shopUser->getCustomer()->willReturn($loggedInCustomer); - $loggedInUserProvider->provide()->willReturn($shopUser); - - $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->can('complete')->willReturn(true); - - $order->setCustomer($loggedInCustomer)->shouldBeCalled(); - $order->setNotes(null)->shouldBeCalled(); - $stateMachine->apply('complete')->shouldBeCalled(); - - $this->handle(new CompleteOrder('ORDERTOKEN', '')); - } - function it_handles_order_completion_with_notes( CustomerInterface $customer, - CustomerRepositoryInterface $customerRepository, - LoggedInShopUserProviderInterface $loggedInUserProvider, - ShopUserInterface $shopUser, + CustomerProviderInterface $customerProvider, OrderInterface $order, OrderRepositoryInterface $orderRepository, StateMachineFactoryInterface $stateMachineFactory, @@ -123,9 +56,7 @@ function it_handles_order_completion_with_notes( ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); - $shopUser->getCustomer()->willReturn($customer); - $loggedInUserProvider->provide()->willReturn($shopUser); + $customerProvider->provide('example@customer.com')->willReturn($customer); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can('complete')->willReturn(true); @@ -134,7 +65,7 @@ function it_handles_order_completion_with_notes( $order->setCustomer($customer)->shouldBeCalled(); $stateMachine->apply('complete')->shouldBeCalled(); - $this->handle(new CompleteOrder('ORDERTOKEN', '', 'Some notes')); + $this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')); } function it_throws_an_exception_if_order_does_not_exist( @@ -142,41 +73,13 @@ function it_throws_an_exception_if_order_does_not_exist( ): void { $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn(null); - $this->shouldThrow(\InvalidArgumentException::class) - ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]) - ; - } - - function it_throws_an_exception_if_the_user_is_logged_in_and_provides_email( - CustomerInterface $customer, - CustomerRepositoryInterface $customerRepository, - LoggedInShopUserProviderInterface $loggedInUserProvider, - CustomerInterface $loggedInCustomer, - ShopUserInterface $shopUser, - OrderInterface $order, - OrderRepositoryInterface $orderRepository, - StateMachineFactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine - ): void { - $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); - - $customerRepository->findOneBy(Argument::any())->shouldNotBeCalled(); - $shopUser->getCustomer()->willReturn($loggedInCustomer); - $loggedInUserProvider->provide()->willReturn($shopUser); - - $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->can('complete')->willReturn(true); - - $order->setCustomer($customer)->shouldNotBeCalled(); - $order->setNotes('Some notes'); - $stateMachine->apply('complete')->shouldNotBeCalled(); - - $this->shouldThrow(\InvalidArgumentException::class) - ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')]) + $this + ->shouldThrow(\InvalidArgumentException::class) + ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]) ; } - function it_throws_an_exception_if_order_cannot_be_addressed( + function it_throws_an_exception_if_order_cannot_be_completed( StateMachineFactoryInterface $stateMachineFactory, OrderInterface $order, OrderRepositoryInterface $orderRepository, @@ -185,8 +88,11 @@ function it_throws_an_exception_if_order_cannot_be_addressed( $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->can('complete')->willReturn(false); + $stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE)->willReturn(false); - $this->shouldThrow(\InvalidArgumentException::class)->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]); + $this + ->shouldThrow(\InvalidArgumentException::class) + ->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]) + ; } } diff --git a/spec/Provider/CustomerProviderSpec.php b/spec/Provider/CustomerProviderSpec.php deleted file mode 100644 index 2a8887983..000000000 --- a/spec/Provider/CustomerProviderSpec.php +++ /dev/null @@ -1,45 +0,0 @@ -beConstructedWith($customerRepository, $customerFactory); - } - - function it_is_customer_provider(): void - { - $this->shouldImplement(CustomerProviderInterface::class); - } - - function it_provides_customer_from_reposiotory(CustomerRepositoryInterface $customerRepository, CustomerInterface $customer): void - { - $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); - - $this->provide('example@customer.com')->shouldReturn($customer); - } - - function it_creates_new_customer_if_it_does_not_exists( - CustomerRepositoryInterface $customerRepository, - FactoryInterface $customerFactory, - CustomerInterface $customer - ): void { - $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn(null); - $customerFactory->createNew()->willReturn($customer); - - $customer->setEmail('example@customer.com')->shouldBeCalled(); - $customerRepository->add($customer)->shouldBeCalled(); - - $this->provide('example@customer.com')->shouldReturn($customer); - } -} diff --git a/spec/Provider/LoggedInShopUserProviderSpec.php b/spec/Provider/LoggedInShopUserProviderSpec.php index f961b4f79..757306db9 100644 --- a/spec/Provider/LoggedInShopUserProviderSpec.php +++ b/spec/Provider/LoggedInShopUserProviderSpec.php @@ -57,9 +57,9 @@ function it_checks_if_shop_user_is_logged_in( $tokenStorage->getToken()->willReturn(null, $token); $token->getUser()->willReturn(null, $anotherUser, $shopUser); - $this->check()->shouldReturn(false); - $this->check()->shouldReturn(false); - $this->check()->shouldReturn(false); - $this->check()->shouldReturn(true); + $this->isUserLoggedIn()->shouldReturn(false); + $this->isUserLoggedIn()->shouldReturn(false); + $this->isUserLoggedIn()->shouldReturn(false); + $this->isUserLoggedIn()->shouldReturn(true); } } diff --git a/spec/Provider/ShopUserAwareCustomerProviderSpec.php b/spec/Provider/ShopUserAwareCustomerProviderSpec.php new file mode 100644 index 000000000..0ad06f658 --- /dev/null +++ b/spec/Provider/ShopUserAwareCustomerProviderSpec.php @@ -0,0 +1,102 @@ +beConstructedWith($customerRepository, $customerFactory, $loggedInShopUserProvider); + } + + function it_is_customer_provider(): void + { + $this->shouldImplement(CustomerProviderInterface::class); + } + + function it_provides_customer_from_reposiotory_if_it_does_not_have_related_shop_user( + CustomerRepositoryInterface $customerRepository, + CustomerInterface $customer, + LoggedInShopUserProviderInterface $loggedInShopUserProvider + ): void { + $loggedInShopUserProvider->isUserLoggedIn()->willReturn(false); + + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); + + $customer->getUser()->willReturn(null); + + $this->provide('example@customer.com')->shouldReturn($customer); + } + + function it_creates_new_customer_if_it_does_not_exists( + CustomerRepositoryInterface $customerRepository, + FactoryInterface $customerFactory, + CustomerInterface $customer, + LoggedInShopUserProviderInterface $loggedInShopUserProvider + ): void { + $loggedInShopUserProvider->isUserLoggedIn()->willReturn(false); + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn(null); + $customerFactory->createNew()->willReturn($customer); + + $customer->setEmail('example@customer.com')->shouldBeCalled(); + $customerRepository->add($customer)->shouldBeCalled(); + + $this->provide('example@customer.com')->shouldReturn($customer); + } + + function it_provides_customer_from_reposiotory_if_it_has_related_shop_user_and_user_is_logged_in( + CustomerInterface $customer, + LoggedInShopUserProviderInterface $loggedInShopUserProvider, + ShopUserInterface $shopUser + ): void { + $loggedInShopUserProvider->isUserLoggedIn()->willReturn(true); + $loggedInShopUserProvider->provide()->willReturn($shopUser); + + $shopUser->getCustomer()->willReturn($customer); + $customer->getEmail()->willReturn('example@customer.com'); + + $this->provide('example@customer.com')->shouldReturn($customer); + } + + function it_throws_an_exception_if_requested_customer_is_not_logged_in_but_has_related_shop_user( + CustomerRepositoryInterface $customerRepository, + CustomerInterface $customer, + LoggedInShopUserProviderInterface $loggedInShopUserProvider, + ShopUserInterface $shopUser + ): void { + $customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); + $loggedInShopUserProvider->isUserLoggedIn()->willReturn(false); + + $customer->getUser()->willReturn($shopUser); + + $this->shouldThrow(WrongUserException::class)->during('provide', ['example@customer.com']); + } + + function it_throws_an_exception_if_requested_customer_is_logged_in_but_customer_is_related_to_another_shop_user( + CustomerInterface $customer, + LoggedInShopUserProviderInterface $loggedInShopUserProvider, + ShopUserInterface $shopUser + ): void { + $loggedInShopUserProvider->isUserLoggedIn()->willReturn(true); + $loggedInShopUserProvider->provide()->willReturn($shopUser); + + $shopUser->getCustomer()->willReturn($customer); + $customer->getEmail()->willReturn('anotherCustomer@customer.com'); + + $this->shouldThrow(WrongUserException::class)->during('provide', ['example@customer.com']); + } +} diff --git a/src/Controller/Checkout/CompleteOrderAction.php b/src/Controller/Checkout/CompleteOrderAction.php index 1008e89f4..9a96899da 100644 --- a/src/Controller/Checkout/CompleteOrderAction.php +++ b/src/Controller/Checkout/CompleteOrderAction.php @@ -12,7 +12,6 @@ use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Core\Exception\TokenNotFoundException; final class CompleteOrderAction { @@ -37,11 +36,15 @@ public function __construct( public function __invoke(Request $request): Response { + if ($this->loggedInUserProvider->isUserLoggedIn()) { + $defaultEmail = $this->loggedInUserProvider->provide()->getEmail(); + } + try { $this->bus->handle( new CompleteOrder( $request->attributes->get('token'), - $request->request->get('email', ''), + $request->request->get('email', $defaultEmail ?? null), $request->request->get('notes') ) ); @@ -52,10 +55,6 @@ public function __invoke(Request $request): Response Response::HTTP_UNAUTHORIZED ) ); - } catch (TokenNotFoundException $notLoggedInException) { - return $this->viewHandler->handle( - View::create('You need to be logged in', Response::HTTP_UNAUTHORIZED) - ); } return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); diff --git a/src/Controller/Order/ShowOrderDetailsAction.php b/src/Controller/Order/ShowOrderDetailsAction.php index 29ada0cae..2e00153ae 100644 --- a/src/Controller/Order/ShowOrderDetailsAction.php +++ b/src/Controller/Order/ShowOrderDetailsAction.php @@ -7,8 +7,8 @@ use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Sylius\Component\Core\Model\ShopUserInterface; -use Sylius\ShopApiPlugin\ViewRepository\Order\PlacedOrderViewRepositoryInterface; use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; +use Sylius\ShopApiPlugin\ViewRepository\Order\PlacedOrderViewRepositoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; diff --git a/src/Exception/WrongUserException.php b/src/Exception/WrongUserException.php index 6522443ed..4c14c354f 100644 --- a/src/Exception/WrongUserException.php +++ b/src/Exception/WrongUserException.php @@ -4,8 +4,6 @@ namespace Sylius\ShopApiPlugin\Exception; -use Exception; - -class WrongUserException extends Exception +final class WrongUserException extends \InvalidArgumentException { } diff --git a/src/Handler/CompleteOrderHandler.php b/src/Handler/CompleteOrderHandler.php index 5c22fe646..e70e836f1 100644 --- a/src/Handler/CompleteOrderHandler.php +++ b/src/Handler/CompleteOrderHandler.php @@ -5,16 +5,11 @@ namespace Sylius\ShopApiPlugin\Handler; use SM\Factory\FactoryInterface as StateMachineFactory; -use Sylius\Component\Core\Model\CustomerInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\OrderCheckoutTransitions; -use Sylius\Component\Core\Repository\CustomerRepositoryInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; -use Sylius\Component\Resource\Factory\FactoryInterface; use Sylius\ShopApiPlugin\Command\CompleteOrder; -use Sylius\ShopApiPlugin\Exception\WrongUserException; -use Sylius\ShopApiPlugin\Provider\LoggedInShopUserProviderInterface; -use Symfony\Component\Security\Core\Exception\TokenNotFoundException; +use Sylius\ShopApiPlugin\Provider\CustomerProviderInterface; use Webmozart\Assert\Assert; final class CompleteOrderHandler @@ -22,30 +17,20 @@ final class CompleteOrderHandler /** @var OrderRepositoryInterface */ private $orderRepository; - /** @var CustomerRepositoryInterface */ - private $customerRepository; + /** @var CustomerProviderInterface */ + private $customerProvider; /** @var StateMachineFactory */ private $stateMachineFactory; - /** @var FactoryInterface */ - private $customerFactory; - - /** @var LoggedInShopUserProviderInterface */ - private $loggedInUserProvider; - public function __construct( OrderRepositoryInterface $orderRepository, - CustomerRepositoryInterface $customerRepository, - FactoryInterface $customerFactory, - LoggedInShopUserProviderInterface $loggedInUserProvider, + CustomerProviderInterface $customerProvider, StateMachineFactory $stateMachineFactory ) { $this->orderRepository = $orderRepository; - $this->customerRepository = $customerRepository; + $this->customerProvider = $customerProvider; $this->stateMachineFactory = $stateMachineFactory; - $this->customerFactory = $customerFactory; - $this->loggedInUserProvider = $loggedInUserProvider; } public function handle(CompleteOrder $completeOrder): void @@ -59,42 +44,10 @@ public function handle(CompleteOrder $completeOrder): void Assert::true($stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE), sprintf('Order with %s token cannot be completed.', $completeOrder->orderToken())); - $customer = $this->getCustomer($completeOrder->email()); + $customer = $this->customerProvider->provide($completeOrder->email()); $order->setNotes($completeOrder->notes()); $order->setCustomer($customer); $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_COMPLETE); } - - private function getCustomer(string $emailAddress): CustomerInterface - { - try { - $loggedInUser = $this->loggedInUserProvider->provide(); - - if ($emailAddress !== '') { - throw new \InvalidArgumentException($emailAddress . ' has to be empty'); - - throw new \InvalidArgumentException('Can not have a logged in user and an email address'); - } - - /** @var CustomerInterface $customer */ - $customer = $loggedInUser->getCustomer(); - - return $customer; - } catch (TokenNotFoundException $notLoggedIn) { - /** @var CustomerInterface|null $customer */ - $customer = $this->customerRepository->findOneBy(['email' => $emailAddress]); - - // If the customer does not exist then it's normal checkout - if ($customer === null) { - /** @var CustomerInterface $customer */ - $customer = $this->customerFactory->createNew(); - $customer->setEmail($emailAddress); - - return $customer; - } - - throw new WrongUserException('Email is already taken'); - } - } } diff --git a/src/Provider/CustomerProvider.php b/src/Provider/CustomerProvider.php deleted file mode 100644 index 62611cd51..000000000 --- a/src/Provider/CustomerProvider.php +++ /dev/null @@ -1,40 +0,0 @@ -customerRepository = $customerRepository; - $this->customerFactory = $customerFactory; - } - - public function provide(string $email): CustomerInterface - { - /** @var CustomerInterface|null */ - $customer = $this->customerRepository->findOneBy(['email' => $email]); - - if (null === $customer) { - /** @var CustomerInterface $customer */ - $customer = $this->customerFactory->createNew(); - $customer->setEmail($email); - - $this->customerRepository->add($customer); - } - - return $customer; - } -} diff --git a/src/Provider/LoggedInShopUserProvider.php b/src/Provider/LoggedInShopUserProvider.php index ba0d5a2ef..17b340443 100644 --- a/src/Provider/LoggedInShopUserProvider.php +++ b/src/Provider/LoggedInShopUserProvider.php @@ -35,7 +35,7 @@ public function provide(): ShopUserInterface return $user; } - public function check(): bool + public function isUserLoggedIn(): bool { $token = $this->tokenStorage->getToken(); diff --git a/src/Provider/LoggedInShopUserProviderInterface.php b/src/Provider/LoggedInShopUserProviderInterface.php index 2a1800fac..879d5821f 100644 --- a/src/Provider/LoggedInShopUserProviderInterface.php +++ b/src/Provider/LoggedInShopUserProviderInterface.php @@ -10,5 +10,5 @@ interface LoggedInShopUserProviderInterface { public function provide(): ShopUserInterface; - public function check(): bool; + public function isUserLoggedIn(): bool; } diff --git a/src/Provider/ShopUserAwareCustomerProvider.php b/src/Provider/ShopUserAwareCustomerProvider.php new file mode 100644 index 000000000..8c8dd8ad8 --- /dev/null +++ b/src/Provider/ShopUserAwareCustomerProvider.php @@ -0,0 +1,67 @@ +customerRepository = $customerRepository; + $this->customerFactory = $customerFactory; + $this->loggedInShopUserProvider = $loggedInShopUserProvider; + } + + public function provide(string $emailAddress): CustomerInterface + { + if ($this->loggedInShopUserProvider->isUserLoggedIn()) { + $loggedInUser = $this->loggedInShopUserProvider->provide(); + + /** @var CustomerInterface $customer */ + $customer = $loggedInUser->getCustomer(); + + if ($customer->getEmail() !== $emailAddress) { + throw new WrongUserException('Cannot finish checkout for other user, if customer is logged in.'); + } + + return $customer; + } + + /** @var CustomerInterface|null $customer */ + $customer = $this->customerRepository->findOneBy(['email' => $emailAddress]); + + if ($customer === null) { + /** @var CustomerInterface $customer */ + $customer = $this->customerFactory->createNew(); + $customer->setEmail($emailAddress); + + $this->customerRepository->add($customer); + + return $customer; + } + + if ($customer->getUser() !== null) { + throw new WrongUserException('Customer already registered. Please log in to finish checkout.'); + } + + return $customer; + } +} diff --git a/src/Resources/config/services/handler/cart.xml b/src/Resources/config/services/handler/cart.xml index c863a0190..152dee849 100644 --- a/src/Resources/config/services/handler/cart.xml +++ b/src/Resources/config/services/handler/cart.xml @@ -101,9 +101,7 @@ - - - + diff --git a/src/Resources/config/services/providers.xml b/src/Resources/config/services/providers.xml index 759ffad84..82a87f51e 100644 --- a/src/Resources/config/services/providers.xml +++ b/src/Resources/config/services/providers.xml @@ -5,9 +5,10 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + class="Sylius\ShopApiPlugin\Provider\ShopUserAwareCustomerProvider"> + loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml', 'customer.yml']); @@ -260,6 +260,57 @@ public function it_disallows_users_to_complete_checkout_for_someone_else() "email": "oliver@queen.com", "notes": "BRING IT AS FAST AS YOU CAN, PLEASE!" } +EOT; + $this->client->request('PUT', sprintf('/shop-api/WEB_GB/checkout/%s/complete', $token), [], [], [ + 'CONTENT_TYPE' => 'application/json', + 'ACCEPT' => 'application/json', + ], $data); + $response = $this->client->getResponse(); + $this->assertResponseCode($response, Response::HTTP_UNAUTHORIZED); + } + + /** + * @test + */ + public function it_disallows_users_to_complete_checkout_for_someone_else() + { + $this->loadFixturesFromFiles(['shop.yml', 'country.yml', 'shipping.yml', 'payment.yml', 'customer.yml']); + + $token = 'SDAOSLEFNWU35H3QLI5325'; + + /** @var CommandBus $bus */ + $bus = $this->get('tactician.commandbus'); + $bus->handle(new PickupCart($token, 'WEB_GB')); + $bus->handle(new PutSimpleItemToCart($token, 'LOGAN_MUG_CODE', 5)); + $bus->handle(new AddressOrder( + $token, + Address::createFromArray([ + 'firstName' => 'Sherlock', + 'lastName' => 'Holmes', + 'city' => 'London', + 'street' => 'Baker Street 221b', + 'countryCode' => 'GB', + 'postcode' => 'NWB', + 'provinceName' => 'Greater London', + ]), Address::createFromArray([ + 'firstName' => 'Sherlock', + 'lastName' => 'Holmes', + 'city' => 'London', + 'street' => 'Baker Street 221b', + 'countryCode' => 'GB', + 'postcode' => 'NWB', + 'provinceName' => 'Greater London', + ]) + )); + $bus->handle(new ChooseShippingMethod($token, 0, 'DHL')); + $bus->handle(new ChoosePaymentMethod($token, 0, 'PBC')); + + $this->logInUser('oliver@queen.com', '123password'); + + $data = <<client->request('PUT', sprintf('/shop-api/WEB_GB/checkout/%s/complete', $token), [], [], [ 'CONTENT_TYPE' => 'application/json',