Program: CWE-918
Project: CVE-2022-24871
Buggy source:
<?php declare(strict_types=1);

namespace Shopware\Core\System\SalesChannel\Context;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartRuleLoader;
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
use Shopware\Core\Checkout\Cart\Event\CartMergedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\System\SalesChannel\Event\SalesChannelContextRestoredEvent;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class CartRestorer
{
    private AbstractSalesChannelContextFactory $factory;

    private SalesChannelContextPersister $contextPersister;

    private CartService $cartService;

    private EventDispatcherInterface $eventDispatcher;

    private CartRuleLoader $cartRuleLoader;

    public function __construct(
        AbstractSalesChannelContextFactory $factory,
        SalesChannelContextPersister $contextPersister,
        CartService $cartService,
        CartRuleLoader $cartRuleLoader,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->factory = $factory;
        $this->contextPersister = $contextPersister;
        $this->cartService = $cartService;
        $this->cartRuleLoader = $cartRuleLoader;
        $this->eventDispatcher = $eventDispatcher;
    }

    public function restore(string $customerId, SalesChannelContext $currentContext): SalesChannelContext
    {
        $customerPayload = $this->contextPersister->load(
            $currentContext->getToken(),
            $currentContext->getSalesChannel()->getId(),
            $customerId
        );

        if (empty($customerPayload) || !($customerPayload['expired'] ?? false) && $customerPayload['token'] === $currentContext->getToken()) {
            return $this->replaceContextToken($customerId, $currentContext);
        }

        $customerContext = $this->factory->create($customerPayload['token'], $currentContext->getSalesChannel()->getId(), $customerPayload);
        if ($customerPayload['expired'] ?? false) {
            $customerContext = $this->replaceContextToken($customerId, $customerContext);
        }

        $guestCart = $this->cartService->getCart($currentContext->getToken(), $currentContext);
        $customerCart = $this->cartService->getCart($customerContext->getToken(), $customerContext);

        if ($guestCart->getLineItems()->count() > 0) {
            $restoredCart = $this->mergeCart($customerCart, $guestCart, $customerContext);
        } else {
            $restoredCart = $this->cartService->recalculate($customerCart, $customerContext);
        }

        $restoredCart->addErrors(...array_values($guestCart->getErrors()->getPersistent()->getElements()));

        $this->deleteGuestContext($currentContext);

        $errors = $restoredCart->getErrors();
        $result = $this->cartRuleLoader->loadByToken($customerContext, $restoredCart->getToken());

        $cartWithErrors = $result->getCart();
        $cartWithErrors->setErrors($errors);
        $this->cartService->setCart($cartWithErrors);

        $this->eventDispatcher->dispatch(new SalesChannelContextRestoredEvent($customerContext));

        return $customerContext;
    }

    private function mergeCart(Cart $customerCart, Cart $guestCart, SalesChannelContext $customerContext): Cart
    {
        $mergeableLineItems = $guestCart->getLineItems()->filter(function (LineItem $item) use ($customerCart) {
            return ($item->getQuantity() > 0 && $item->isStackable()) || !$customerCart->has($item->getId());
        });

        $errors = $customerCart->getErrors();
        $customerCart->setErrors(new ErrorCollection());

        $customerCartClone = clone $customerCart;
        $customerCart->setErrors($errors);
        $customerCartClone->setErrors($errors);

        $mergedCart = $this->cartService->add($customerCart, $mergeableLineItems->getElements(), $customerContext);

        $this->eventDispatcher->dispatch(new CartMergedEvent($mergedCart, $customerContext, $customerCartClone));

        return $mergedCart;
    }

    private function replaceContextToken(string $customerId, SalesChannelContext $currentContext): SalesChannelContext
    {
        $newToken = $this->contextPersister->replace($currentContext->getToken(), $currentContext);

        $currentContext->assign([
            'token' => $newToken,
        ]);

        $this->contextPersister->save(
            $newToken,
            [
                'customerId' => $customerId,
                'billingAddressId' => null,
                'shippingAddressId' => null,
            ],
            $currentContext->getSalesChannel()->getId(),
            $customerId
        );

        return $currentContext;
    }

    private function deleteGuestContext(SalesChannelContext $guestContext): void
    {
        $this->cartService->deleteCart($guestContext);
        $this->contextPersister->delete($guestContext->getToken());
    }
}


Fixed version (ground truth):
<?php declare(strict_types=1);

namespace Shopware\Core\System\SalesChannel\Context;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartRuleLoader;
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
use Shopware\Core\Checkout\Cart\Event\CartMergedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\System\SalesChannel\Event\SalesChannelContextRestoredEvent;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class CartRestorer
{
    private AbstractSalesChannelContextFactory $factory;

    private SalesChannelContextPersister $contextPersister;

    private CartService $cartService;

    private EventDispatcherInterface $eventDispatcher;

    private CartRuleLoader $cartRuleLoader;

    public function __construct(
        AbstractSalesChannelContextFactory $factory,
        SalesChannelContextPersister $contextPersister,
        CartService $cartService,
        CartRuleLoader $cartRuleLoader,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->factory = $factory;
        $this->contextPersister = $contextPersister;
        $this->cartService = $cartService;
        $this->cartRuleLoader = $cartRuleLoader;
        $this->eventDispatcher = $eventDispatcher;
    }

    public function restore(string $customerId, SalesChannelContext $currentContext): SalesChannelContext
    {
        $customerPayload = $this->contextPersister->load(
            $currentContext->getToken(),
            $currentContext->getSalesChannel()->getId(),
            $customerId
        );

        if (empty($customerPayload) || !empty($customerPayload['permissions']) || !($customerPayload['expired'] ?? false) && $customerPayload['token'] === $currentContext->getToken()) {
            return $this->replaceContextToken($customerId, $currentContext);
        }

        $customerContext = $this->factory->create($customerPayload['token'], $currentContext->getSalesChannel()->getId(), $customerPayload);
        if ($customerPayload['expired'] ?? false) {
            $customerContext = $this->replaceContextToken($customerId, $customerContext);
        }

        $guestCart = $this->cartService->getCart($currentContext->getToken(), $currentContext);
        $customerCart = $this->cartService->getCart($customerContext->getToken(), $customerContext);

        if ($guestCart->getLineItems()->count() > 0) {
            $restoredCart = $this->mergeCart($customerCart, $guestCart, $customerContext);
        } else {
            $restoredCart = $this->cartService->recalculate($customerCart, $customerContext);
        }

        $restoredCart->addErrors(...array_values($guestCart->getErrors()->getPersistent()->getElements()));

        $this->deleteGuestContext($currentContext);

        $errors = $restoredCart->getErrors();
        $result = $this->cartRuleLoader->loadByToken($customerContext, $restoredCart->getToken());

        $cartWithErrors = $result->getCart();
        $cartWithErrors->setErrors($errors);
        $this->cartService->setCart($cartWithErrors);

        $this->eventDispatcher->dispatch(new SalesChannelContextRestoredEvent($customerContext));

        return $customerContext;
    }

    private function mergeCart(Cart $customerCart, Cart $guestCart, SalesChannelContext $customerContext): Cart
    {
        $mergeableLineItems = $guestCart->getLineItems()->filter(function (LineItem $item) use ($customerCart) {
            return ($item->getQuantity() > 0 && $item->isStackable()) || !$customerCart->has($item->getId());
        });

        $errors = $customerCart->getErrors();
        $customerCart->setErrors(new ErrorCollection());

        $customerCartClone = clone $customerCart;
        $customerCart->setErrors($errors);
        $customerCartClone->setErrors($errors);

        $mergedCart = $this->cartService->add($customerCart, $mergeableLineItems->getElements(), $customerContext);

        $this->eventDispatcher->dispatch(new CartMergedEvent($mergedCart, $customerContext, $customerCartClone));

        return $mergedCart;
    }

    private function replaceContextToken(string $customerId, SalesChannelContext $currentContext): SalesChannelContext
    {
        $newToken = $this->contextPersister->replace($currentContext->getToken(), $currentContext);

        $currentContext->assign([
            'token' => $newToken,
        ]);

        $this->contextPersister->save(
            $newToken,
            [
                'customerId' => $customerId,
                'billingAddressId' => null,
                'shippingAddressId' => null,
                'permissions' => [],
            ],
            $currentContext->getSalesChannel()->getId(),
            $customerId
        );

        return $currentContext;
    }

    private function deleteGuestContext(SalesChannelContext $guestContext): void
    {
        $this->cartService->deleteCart($guestContext);
        $this->contextPersister->delete($guestContext->getToken());
    }
}