custom/plugins/SwagPlatformSecurity/src/Fixes/NEXT19820/CustomerTokenSubscriber.php line 67

Open in your IDE?
  1. <?php
  2. namespace Swag\Security\Fixes\NEXT19820;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Checkout\Customer\CustomerEvents;
  5. use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  8. use Shopware\Core\PlatformRequest;
  9. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister;
  10. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpFoundation\RequestStack;
  13. class CustomerTokenSubscriber implements EventSubscriberInterface
  14. {
  15.     /**
  16.      * @var Connection
  17.      */
  18.     protected $connection;
  19.     /**
  20.      * @var RequestStack
  21.      */
  22.     protected $requestStack;
  23.     /**
  24.      * @var SalesChannelContextPersister
  25.      */
  26.     protected $contextPersister;
  27.     public static function getSubscribedEvents(): array
  28.     {
  29.         return [
  30.             CustomerEvents::CUSTOMER_WRITTEN_EVENT => 'onCustomerWritten',
  31.             CustomerEvents::CUSTOMER_DELETED_EVENT => 'onCustomerDeleted',
  32.         ];
  33.     }
  34.     public function __construct(Connection $connectionRequestStack $requestStackSalesChannelContextPersister $contextPersister)
  35.     {
  36.         $this->connection $connection;
  37.         $this->requestStack $requestStack;
  38.         $this->contextPersister $contextPersister;
  39.     }
  40.     public function onCustomerWritten(EntityWrittenEvent $event): void
  41.     {
  42.         foreach ($event->getWriteResults() as $writeResult) {
  43.             if ($writeResult->getOperation() !== EntityWriteResult::OPERATION_UPDATE) {
  44.                 continue;
  45.             }
  46.             $payload $writeResult->getPayload();
  47.             if (!$this->customerCredentialsChanged($payload)) {
  48.                 continue;
  49.             }
  50.             $customerId $payload['id'];
  51.             $newToken $this->invalidateUsingSession($customerId);
  52.             $this->revokeAllCustomerTokens($customerId$newToken);
  53.         }
  54.     }
  55.     public function onCustomerDeleted(EntityDeletedEvent $event): void
  56.     {
  57.         foreach ($event->getIds() as $customerId) {
  58.             $this->revokeAllCustomerTokens($customerId);
  59.         }
  60.     }
  61.     private function customerCredentialsChanged(array $payload): bool
  62.     {
  63.         return isset($payload['password']);
  64.     }
  65.     private function invalidateUsingSession(string $customerId): ?string
  66.     {
  67.         $master $this->requestStack->getMasterRequest();
  68.         if (!$master) {
  69.             return null;
  70.         }
  71.         // Is not a storefront request
  72.         if (!$master->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)) {
  73.             return null;
  74.         }
  75.         /** @var SalesChannelContext $context */
  76.         $context $master->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  77.         // Not loggedin skip
  78.         if ($context->getCustomer() === null) {
  79.             return null;
  80.         }
  81.         // The written customer is not the same as logged-in. We don't modify the user session
  82.         if ($context->getCustomer()->getId() !== $customerId) {
  83.             return null;
  84.         }
  85.         $token $context->getToken();
  86.         $newToken $this->contextPersister->replace($token$context);
  87.         $context->assign([
  88.             'token' => $newToken,
  89.         ]);
  90.         if (!$master->hasSession()) {
  91.             return null;
  92.         }
  93.         $session $master->getSession();
  94.         $session->migrate();
  95.         $session->set('sessionId'$session->getId());
  96.         $session->set(PlatformRequest::HEADER_CONTEXT_TOKEN$newToken);
  97.         $master->headers->set(PlatformRequest::HEADER_CONTEXT_TOKEN$newToken);
  98.         return $newToken;
  99.     }
  100.     public function revokeAllCustomerTokens(string $customerId, ?string ...$preserveTokens): void
  101.     {
  102.         $preserveTokens array_filter($preserveTokens);
  103.         $revokeParams = [
  104.             'customerId' => null,
  105.             'billingAddressId' => null,
  106.             'shippingAddressId' => null,
  107.         ];
  108.         $qb $this->connection->createQueryBuilder();
  109.         $qb
  110.             ->update('sales_channel_api_context')
  111.             ->set('payload'':payload')
  112.             ->where('JSON_EXTRACT(payload, :customerPath) = :customerId')
  113.             ->setParameter(':payload'json_encode($revokeParams))
  114.             ->setParameter(':customerPath''$.customerId')
  115.             ->setParameter(':customerId'$customerId);
  116.         // keep tokens valid, which are given in $preserveTokens
  117.         if ($preserveTokens) {
  118.             $qb
  119.                 ->andWhere($qb->expr()->notIn('token'':preserveTokens'))
  120.                 ->setParameter(':preserveTokens'$preserveTokensConnection::PARAM_STR_ARRAY);
  121.         }
  122.         $qb->execute();
  123.     }
  124. }