Sylius: Can't simulate order switching user

Created on 2 Jun 2017  路  5Comments  路  Source: Sylius/Sylius

The problem is the following.

I log in as a user that has role to switch user. Then I switch user with ?_switch_user and make an order but when I finish the order is places with the customer of the original auntheticated user, not the switched user.

Critical Potential Bug

Most helpful comment

@pjedrzejewski I paste here the solution used in our project (changing namspaces) in case it is usefull.

The listener for switch user

<?php

namespace Acme\EventListener;

use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class UserSwitchedListener implements EventSubscriberInterface
{
    /**
     * @var SessionInterface
     */
    protected $session;

    protected $sessionKeyName;

    /**
     * @var ChannelContextInterface
     */
    protected $channelContext;

    /**
     * @var OrderRepositoryInterface
     */
    protected $orderRepository;

    /**
     * UserSwitchedListener constructor.
     * @param CartContextInterface $catContext
     */
    public function __construct(
        SessionInterface $session,
        $sessionKeyName,
        ChannelContextInterface $channelContext,
        OrderRepositoryInterface $orderRepository
    )
    {
        $this->session = $session;
        $this->sessionKeyName = $sessionKeyName;
        $this->channelContext = $channelContext;
        $this->orderRepository = $orderRepository;
    }

    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * array('eventName' => 'methodName')
     *  * array('eventName' => array('methodName', $priority))
     *  * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
     *
     * @return array The event names to listen to
     */
    public static function getSubscribedEvents()
    {
        return array(
            SecurityEvents::SWITCH_USER => 'userSwitched'
        );
    }

    public function userSwitched(SwitchUserEvent $switchUserEvent)
    {
        $customer = $switchUserEvent->getTargetUser()->getCustomer();

        $customerCart = $this->orderRepository->findLatestCartOfCustomer($customer);

        if($customerCart != null){
            $this->setSessionCartId($customerCart->getId());
        }
    }

    protected function setSessionCartId($cartId)
    {
        $sessionCartKey = $this->getSessionCartKey();
        $this->session->set($sessionCartKey, $cartId);
    }

    protected function getSessionCartKey()
    {
        $channel = $this->channelContext->getChannel();
        return sprintf('%s.%s', $this->sessionKeyName, $channel->getCode());
    }
}
' ' '

In a custom OrderRepositoy I added the following method:

```php
public function findLatestCartOfCustomer($customer)
    {
        return $this->createQueryBuilder('o')
            ->andWhere('o.state = :state')
            ->andWhere('o.customer = :customer')
            ->setMaxResults(1)
            ->orderBy('o.id', 'desc')
            ->setParameter('state', OrderInterface::STATE_CART)
            ->setParameter('customer', $customer->getId())
            ->getQuery()
            ->getOneOrNullResult()
            ;
    }

Then I register the service the following way:

acme.event_listener.user_switched_listener:
        class: Acme\EventListener\UserSwitchedListener
        arguments: ['@session', '_sylius.cart', '@sylius.context.channel.cached', '@sylius.repository.order']
        tags:
            - { name: kernel.event_subscriber }

I'm sorry not making a pull request, I'm fixing it on alpha.2 versi贸n since we are using it in production.

All 5 comments

Yes I have the same problem, in version 0.18, I could switch by customer and to simulate a payment from my computer... now, If I switch user, the Order belong to me

I thing one way to solve it is to add a Listener that listens to SecurityEvents::SWITCH_USER event.

Then try to recover the las order with cart state for the user and set it in session the id of the cart.

The problem I see is you can not know the cart is abandoned.

Another solution is to add a context to the CompositeContext that gets the cart from the database and just in case the user is not authenticated get the cart from session.

@pjedrzejewski I paste here the solution used in our project (changing namspaces) in case it is usefull.

The listener for switch user

<?php

namespace Acme\EventListener;

use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class UserSwitchedListener implements EventSubscriberInterface
{
    /**
     * @var SessionInterface
     */
    protected $session;

    protected $sessionKeyName;

    /**
     * @var ChannelContextInterface
     */
    protected $channelContext;

    /**
     * @var OrderRepositoryInterface
     */
    protected $orderRepository;

    /**
     * UserSwitchedListener constructor.
     * @param CartContextInterface $catContext
     */
    public function __construct(
        SessionInterface $session,
        $sessionKeyName,
        ChannelContextInterface $channelContext,
        OrderRepositoryInterface $orderRepository
    )
    {
        $this->session = $session;
        $this->sessionKeyName = $sessionKeyName;
        $this->channelContext = $channelContext;
        $this->orderRepository = $orderRepository;
    }

    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * array('eventName' => 'methodName')
     *  * array('eventName' => array('methodName', $priority))
     *  * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
     *
     * @return array The event names to listen to
     */
    public static function getSubscribedEvents()
    {
        return array(
            SecurityEvents::SWITCH_USER => 'userSwitched'
        );
    }

    public function userSwitched(SwitchUserEvent $switchUserEvent)
    {
        $customer = $switchUserEvent->getTargetUser()->getCustomer();

        $customerCart = $this->orderRepository->findLatestCartOfCustomer($customer);

        if($customerCart != null){
            $this->setSessionCartId($customerCart->getId());
        }
    }

    protected function setSessionCartId($cartId)
    {
        $sessionCartKey = $this->getSessionCartKey();
        $this->session->set($sessionCartKey, $cartId);
    }

    protected function getSessionCartKey()
    {
        $channel = $this->channelContext->getChannel();
        return sprintf('%s.%s', $this->sessionKeyName, $channel->getCode());
    }
}
' ' '

In a custom OrderRepositoy I added the following method:

```php
public function findLatestCartOfCustomer($customer)
    {
        return $this->createQueryBuilder('o')
            ->andWhere('o.state = :state')
            ->andWhere('o.customer = :customer')
            ->setMaxResults(1)
            ->orderBy('o.id', 'desc')
            ->setParameter('state', OrderInterface::STATE_CART)
            ->setParameter('customer', $customer->getId())
            ->getQuery()
            ->getOneOrNullResult()
            ;
    }

Then I register the service the following way:

acme.event_listener.user_switched_listener:
        class: Acme\EventListener\UserSwitchedListener
        arguments: ['@session', '_sylius.cart', '@sylius.context.channel.cached', '@sylius.repository.order']
        tags:
            - { name: kernel.event_subscriber }

I'm sorry not making a pull request, I'm fixing it on alpha.2 versi贸n since we are using it in production.

@jdeveloper Thanks for reporting!

This is still a problem, and the jdeveloper's solution still works.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mikemix picture mikemix  路  3Comments

stefandoorn picture stefandoorn  路  3Comments

igormukhingmailcom picture igormukhingmailcom  路  3Comments

xleliberty picture xleliberty  路  3Comments

bnd170 picture bnd170  路  3Comments