Broken Access Control order API in Shopware
Description
Shopware is an open headless commerce platform. In the Shopware CMS, the state handler for orders fails to sufficiently verify user authorizations for actions that modify the payment, delivery, and/or order status. Due to this inadequate implementation, users lacking 'write' permissions for orders are still able to change the order state. This issue has been addressed and users are advised to update to Shopware 6.5.7.4. For older versions of 6.1, 6.2, 6.3 and 6.4 corresponding security measures are also available via a plugin. For the full range of functions, we recommend updating to the latest Shopware version.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
shopware/corePackagist | < 6.5.7.4 | 6.5.7.4 |
shopware/platformPackagist | < 6.5.7.4 | 6.5.7.4 |
Affected products
1Patches
2fb25e24ca516NEXT-32889 - Fix privileges for state machine
5 files changed · +97 −14
changelog/_unreleased/2024-01-05-fix-privileges-for-state-machine.md+8 −0 added@@ -0,0 +1,8 @@ +--- +title: Fix privileges for state machine +issue: NEXT-32889 +author: Max Stegmeyer +author_email: m.stegmeyer@shopware.com +--- +# Core +* Added privilege check for state machine transitions
src/Core/System/StateMachine/Api/StateMachineActionController.php+16 −0 modified@@ -2,13 +2,16 @@ namespace Shopware\Core\System\StateMachine\Api; +use Shopware\Core\Framework\Api\Acl\Role\AclRoleDefinition; +use Shopware\Core\Framework\Api\ApiException; use Shopware\Core\Framework\Api\Response\ResponseFactoryInterface; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\Log\Package; use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateDefinition; use Shopware\Core\System\StateMachine\Aggregation\StateMachineTransition\StateMachineTransitionEntity; +use Shopware\Core\System\StateMachine\StateMachineException; use Shopware\Core\System\StateMachine\StateMachineRegistry; use Shopware\Core\System\StateMachine\Transition; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -23,6 +26,8 @@ class StateMachineActionController extends AbstractController { /** * @var StateMachineRegistry + * + * @deprecated tag:v6.7.0 - will be private and typed */ protected $stateMachineRegistry; @@ -43,6 +48,7 @@ public function getAvailableTransitions( string $entityName, string $entityId ): JsonResponse { + $this->validatePrivilege($entityName, AclRoleDefinition::PRIVILEGE_READ, $context); $stateFieldName = (string) $request->query->get('stateFieldName', 'stateId'); $availableTransitions = $this->stateMachineRegistry->getAvailableTransitions( @@ -84,6 +90,8 @@ public function transitionState( string $entityId, string $transition ): Response { + $this->validatePrivilege($entityName, AclRoleDefinition::PRIVILEGE_UPDATE, $context); + $stateFieldName = (string) $request->query->get('stateFieldName', 'stateId'); $stateMachineStateCollection = $this->stateMachineRegistry->transition( new Transition( @@ -103,4 +111,12 @@ public function transitionState( $context ); } + + private function validatePrivilege(string $entityName, string $privilege, Context $context): void + { + $permission = $entityName . ':' . $privilege; + if (!$context->isAllowed($permission)) { + throw StateMachineException::missingPrivileges([$permission]); + } + } }
src/Core/System/StateMachine/StateMachineException.php+10 −0 modified@@ -2,9 +2,11 @@ namespace Shopware\Core\System\StateMachine; +use Shopware\Core\Framework\Api\Exception\MissingPrivilegeException; use Shopware\Core\Framework\Feature; use Shopware\Core\Framework\HttpException; use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\ShopwareHttpException; use Shopware\Core\System\StateMachine\Exception\IllegalTransitionException; use Shopware\Core\System\StateMachine\Exception\StateMachineInvalidEntityIdException; use Shopware\Core\System\StateMachine\Exception\StateMachineInvalidStateFieldException; @@ -115,4 +117,12 @@ public static function unnecessaryTransition(string $transition): UnnecessaryTra { return new UnnecessaryTransitionException($transition); } + + /** + * @param string[] $permissions + */ + public static function missingPrivileges(array $permissions): ShopwareHttpException + { + return new MissingPrivilegeException($permissions); + } }
tests/integration/Core/System/StateMachine/Api/StateMachineActionControllerTest.php+4 −14 renamed@@ -1,6 +1,6 @@ <?php declare(strict_types=1); -namespace Shopware\Core\System\Test\StateMachine\Api; +namespace Shopware\Tests\Integration\Core\System\StateMachine\Api; use Doctrine\DBAL\Connection; use PHPUnit\Framework\TestCase; @@ -46,20 +46,11 @@ class StateMachineActionControllerTest extends TestCase use IntegrationTestBehaviour; use TaxAddToSalesChannelTestBehaviour; - /** - * @var EntityRepository - */ - private $orderRepository; + private EntityRepository $orderRepository; - /** - * @var EntityRepository - */ - private $customerRepository; + private EntityRepository $customerRepository; - /** - * @var EntityRepository - */ - private $stateMachineHistoryRepository; + private EntityRepository $stateMachineHistoryRepository; protected function setUp(): void { @@ -102,7 +93,6 @@ public function testGetAvailableStates(): void public function testTransitionToAllowedState(): void { - // TODO $context = Context::createDefaultContext(); $customerId = $this->createCustomer($context); $orderId = $this->createOrder($customerId, $context);
tests/unit/Core/System/StateMachine/Api/StateMachineActionControllerTest.php+59 −0 added@@ -0,0 +1,59 @@ +<?php declare(strict_types=1); + +namespace Shopware\Tests\Unit\Core\System\StateMachine\Api; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Api\Context\AdminApiSource; +use Shopware\Core\Framework\Api\Exception\MissingPrivilegeException; +use Shopware\Core\Framework\Api\Response\ResponseFactoryInterface; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\System\StateMachine\Api\StateMachineActionController; +use Shopware\Core\System\StateMachine\StateMachineRegistry; +use Symfony\Component\HttpFoundation\Request; + +/** + * @internal + */ +#[Package('checkout')] +#[CoversClass(StateMachineActionController::class)] +class StateMachineActionControllerTest extends TestCase +{ + public function testTransitionWithoutPrivileges(): void + { + $this->expectException(MissingPrivilegeException::class); + $this->expectExceptionMessage('{"message":"Missing privilege","missingPrivileges":["order:update"]}'); + + $controller = new StateMachineActionController( + $this->createMock(StateMachineRegistry::class), + $this->createMock(DefinitionInstanceRegistry::class), + ); + $controller->transitionState( + new Request(), + Context::createDefaultContext(new AdminApiSource(null)), + $this->createMock(ResponseFactoryInterface::class), + 'order', + '1234', + 'process', + ); + } + + public function testGetAvailableTransitionsWithoutPrivileges(): void + { + $this->expectException(MissingPrivilegeException::class); + $this->expectExceptionMessage('{"message":"Missing privilege","missingPrivileges":["order:read"]}'); + + $controller = new StateMachineActionController( + $this->createMock(StateMachineRegistry::class), + $this->createMock(DefinitionInstanceRegistry::class), + ); + $controller->getAvailableTransitions( + new Request(), + Context::createDefaultContext(new AdminApiSource(null)), + 'order', + '1234', + ); + } +}
78142489264fNEXT-32889 - Fix privileges for state machine
3 files changed · +26 −436
System/StateMachine/Api/StateMachineActionController.php+16 −0 modified@@ -2,13 +2,16 @@ namespace Shopware\Core\System\StateMachine\Api; +use Shopware\Core\Framework\Api\Acl\Role\AclRoleDefinition; +use Shopware\Core\Framework\Api\ApiException; use Shopware\Core\Framework\Api\Response\ResponseFactoryInterface; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\Log\Package; use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateDefinition; use Shopware\Core\System\StateMachine\Aggregation\StateMachineTransition\StateMachineTransitionEntity; +use Shopware\Core\System\StateMachine\StateMachineException; use Shopware\Core\System\StateMachine\StateMachineRegistry; use Shopware\Core\System\StateMachine\Transition; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -23,6 +26,8 @@ class StateMachineActionController extends AbstractController { /** * @var StateMachineRegistry + * + * @deprecated tag:v6.7.0 - will be private and typed */ protected $stateMachineRegistry; @@ -43,6 +48,7 @@ public function getAvailableTransitions( string $entityName, string $entityId ): JsonResponse { + $this->validatePrivilege($entityName, AclRoleDefinition::PRIVILEGE_READ, $context); $stateFieldName = (string) $request->query->get('stateFieldName', 'stateId'); $availableTransitions = $this->stateMachineRegistry->getAvailableTransitions( @@ -84,6 +90,8 @@ public function transitionState( string $entityId, string $transition ): Response { + $this->validatePrivilege($entityName, AclRoleDefinition::PRIVILEGE_UPDATE, $context); + $stateFieldName = (string) $request->query->get('stateFieldName', 'stateId'); $stateMachineStateCollection = $this->stateMachineRegistry->transition( new Transition( @@ -103,4 +111,12 @@ public function transitionState( $context ); } + + private function validatePrivilege(string $entityName, string $privilege, Context $context): void + { + $permission = $entityName . ':' . $privilege; + if (!$context->isAllowed($permission)) { + throw StateMachineException::missingPrivileges([$permission]); + } + } }
System/StateMachine/StateMachineException.php+10 −0 modified@@ -2,9 +2,11 @@ namespace Shopware\Core\System\StateMachine; +use Shopware\Core\Framework\Api\Exception\MissingPrivilegeException; use Shopware\Core\Framework\Feature; use Shopware\Core\Framework\HttpException; use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\ShopwareHttpException; use Shopware\Core\System\StateMachine\Exception\IllegalTransitionException; use Shopware\Core\System\StateMachine\Exception\StateMachineInvalidEntityIdException; use Shopware\Core\System\StateMachine\Exception\StateMachineInvalidStateFieldException; @@ -115,4 +117,12 @@ public static function unnecessaryTransition(string $transition): UnnecessaryTra { return new UnnecessaryTransitionException($transition); } + + /** + * @param string[] $permissions + */ + public static function missingPrivileges(array $permissions): ShopwareHttpException + { + return new MissingPrivilegeException($permissions); + } }
System/Test/StateMachine/Api/StateMachineActionControllerTest.php+0 −436 removed@@ -1,436 +0,0 @@ -<?php declare(strict_types=1); - -namespace Shopware\Core\System\Test\StateMachine\Api; - -use Doctrine\DBAL\Connection; -use PHPUnit\Framework\TestCase; -use Shopware\Core\Checkout\Cart\LineItemFactoryHandler\ProductLineItemFactory; -use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice; -use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice; -use Shopware\Core\Checkout\Cart\PriceDefinitionFactory; -use Shopware\Core\Checkout\Cart\Rule\AlwaysValidRule; -use Shopware\Core\Checkout\Cart\SalesChannel\CartService; -use Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection; -use Shopware\Core\Checkout\Cart\Tax\Struct\TaxRuleCollection; -use Shopware\Core\Checkout\Order\OrderDefinition; -use Shopware\Core\Checkout\Order\OrderEntity; -use Shopware\Core\Checkout\Order\OrderStates; -use Shopware\Core\Content\Product\Aggregate\ProductVisibility\ProductVisibilityDefinition; -use Shopware\Core\Defaults; -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; -use Shopware\Core\Framework\DataAbstractionLayer\Pricing\CashRoundingConfig; -use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; -use Shopware\Core\Framework\Feature; -use Shopware\Core\Framework\Test\TestCaseBase\AdminApiTestBehaviour; -use Shopware\Core\Framework\Test\TestCaseBase\CountryAddToSalesChannelTestBehaviour; -use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour; -use Shopware\Core\Framework\Test\TestCaseBase\TaxAddToSalesChannelTestBehaviour; -use Shopware\Core\Framework\Uuid\Uuid; -use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; -use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory; -use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService; -use Shopware\Core\System\StateMachine\Aggregation\StateMachineHistory\StateMachineHistoryEntity; -use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateEntity; -use Shopware\Core\System\StateMachine\Loader\InitialStateIdLoader; -use Shopware\Core\Test\TestDefaults; -use Symfony\Component\HttpFoundation\Response; - -/** - * @internal - */ -class StateMachineActionControllerTest extends TestCase -{ - use AdminApiTestBehaviour; - use CountryAddToSalesChannelTestBehaviour; - use IntegrationTestBehaviour; - use TaxAddToSalesChannelTestBehaviour; - - /** - * @var EntityRepository - */ - private $orderRepository; - - /** - * @var EntityRepository - */ - private $customerRepository; - - /** - * @var EntityRepository - */ - private $stateMachineHistoryRepository; - - protected function setUp(): void - { - parent::setUp(); - - $this->orderRepository = $this->getContainer()->get('order.repository'); - $this->customerRepository = $this->getContainer()->get('customer.repository'); - $this->stateMachineHistoryRepository = $this->getContainer()->get('state_machine_history.repository'); - } - - public function testOrderNotFoundException(): void - { - $this->getBrowser()->request('GET', '/api/order/' . Uuid::randomHex() . '/actions/state'); - - $response = $this->getBrowser()->getResponse()->getContent(); - static::assertIsString($response); - $response = json_decode($response, true, 512, \JSON_THROW_ON_ERROR); - - static::assertEquals(Response::HTTP_NOT_FOUND, $this->getBrowser()->getResponse()->getStatusCode()); - static::assertArrayHasKey('errors', $response); - } - - public function testGetAvailableStates(): void - { - $context = Context::createDefaultContext(); - $customerId = $this->createCustomer($context); - $orderId = $this->createOrder($customerId, $context); - - $this->getBrowser()->request('GET', '/api/_action/state-machine/order/' . $orderId . '/state'); - - static::assertEquals(200, $this->getBrowser()->getResponse()->getStatusCode()); - $response = $this->getBrowser()->getResponse()->getContent(); - static::assertIsString($response); - $response = json_decode($response, true, 512, \JSON_THROW_ON_ERROR); - - static::assertCount(2, $response['transitions']); - static::assertEquals('cancel', $response['transitions'][0]['actionName']); - static::assertStringEndsWith('/_action/state-machine/order/' . $orderId . '/state/cancel', $response['transitions'][0]['url']); - } - - public function testTransitionToAllowedState(): void - { - // TODO - $context = Context::createDefaultContext(); - $customerId = $this->createCustomer($context); - $orderId = $this->createOrder($customerId, $context); - - $this->getBrowser()->request('GET', '/api/_action/state-machine/order/' . $orderId . '/state'); - - $response = $this->getBrowser()->getResponse()->getContent(); - static::assertIsString($response); - $response = json_decode($response, true, 512, \JSON_THROW_ON_ERROR); - - $actionUrl = $response['transitions'][0]['url']; - $transitionTechnicalName = $response['transitions'][0]['technicalName']; - - $this->getBrowser()->request('POST', $actionUrl); - - $responseString = $this->getBrowser()->getResponse()->getContent(); - static::assertIsString($responseString); - $response = json_decode($responseString, true, 512, \JSON_THROW_ON_ERROR); - - static::assertEquals( - Response::HTTP_OK, - $this->getBrowser()->getResponse()->getStatusCode(), - $responseString - ); - - $stateId = $response['data']['id'] ?? ''; - static::assertTrue(Uuid::isValid($stateId)); - - $destinationStateTechnicalName = $response['data']['attributes']['technicalName']; - static::assertEquals($transitionTechnicalName, $destinationStateTechnicalName); - - // test whether the state history was written - $criteria = new Criteria(); - $criteria->addAssociation('fromStateMachineState'); - $criteria->addAssociation('toStateMachineState'); - - $history = $this->stateMachineHistoryRepository->search($criteria, $context); - - static::assertCount(1, $history->getElements(), 'Expected history to be written'); - /** @var StateMachineHistoryEntity $historyEntry */ - $historyEntry = array_values($history->getElements())[0]; - - $toStateMachineState = $historyEntry->getToStateMachineState(); - static::assertInstanceOf(StateMachineStateEntity::class, $toStateMachineState); - static::assertEquals($destinationStateTechnicalName, $toStateMachineState->getTechnicalName()); - - static::assertEquals($this->getContainer()->get(OrderDefinition::class)->getEntityName(), $historyEntry->getEntityName()); - - if (!Feature::isActive('v6.6.0.0')) { - static::assertEquals($orderId, $historyEntry->getEntityId()['id']); - static::assertEquals(Defaults::LIVE_VERSION, $historyEntry->getEntityId()['version_id']); - - return; - } - - static::assertEquals($orderId, $historyEntry->getReferencedId()); - static::assertEquals(Defaults::LIVE_VERSION, $historyEntry->getReferencedVersionId()); - } - - public function testTransitionToNotAllowedState(): void - { - $context = Context::createDefaultContext(); - $customerId = $this->createCustomer($context); - $orderId = $this->createOrder($customerId, $context); - - $this->getBrowser()->request('POST', '/api/_action/state-machine/order/' . $orderId . '/state/foo'); - - $response = $this->getBrowser()->getResponse()->getContent(); - static::assertIsString($response); - $response = json_decode($response, true, 512, \JSON_THROW_ON_ERROR); - - static::assertEquals(Response::HTTP_BAD_REQUEST, $this->getBrowser()->getResponse()->getStatusCode()); - static::assertArrayHasKey('errors', $response); - } - - public function testOrderCartDe(): void - { - $context = Context::createDefaultContext(); - $customerId = $this->createCustomer($context); - - $cartService = $this->getContainer()->get(CartService::class); - - $options = [ - SalesChannelContextService::LANGUAGE_ID => $this->getDeDeLanguageId(), - SalesChannelContextService::CUSTOMER_ID => $customerId, - SalesChannelContextService::SHIPPING_METHOD_ID => $this->createShippingMethod(), - ]; - - $salesChannelContext = $this->getContainer()->get(SalesChannelContextFactory::class) - ->create(Uuid::randomHex(), TestDefaults::SALES_CHANNEL, $options); - - $productId = Uuid::randomHex(); - $product = [ - 'id' => $productId, - 'productNumber' => $productId, - 'name' => [ - 'de-DE' => 'test', - 'en-GB' => 'test', - ], - 'active' => true, - 'visibilities' => [ - ['salesChannelId' => $salesChannelContext->getSalesChannel()->getId(), 'visibility' => ProductVisibilityDefinition::VISIBILITY_ALL], - ], - 'stock' => 10, - 'price' => [ - ['currencyId' => Defaults::CURRENCY, 'gross' => 100, 'net' => 100, 'linked' => false], - ], - 'tax' => ['id' => Uuid::randomHex(), 'name' => 'test', 'taxRate' => 18], - 'manufacturer' => [ - 'name' => [ - 'de-DE' => 'test', - 'en-GB' => 'test', - ], - ], - ]; - - $this->getContainer()->get('product.repository') - ->create([$product], $salesChannelContext->getContext()); - $this->addTaxDataToSalesChannel($salesChannelContext, $product['tax']); - - $lineItem = (new ProductLineItemFactory(new PriceDefinitionFactory()))->create(['id' => $productId, 'referencedId' => $productId], $salesChannelContext); - - $cart = $cartService->getCart($salesChannelContext->getToken(), $salesChannelContext); - - $cart = $cartService->add($cart, $lineItem, $salesChannelContext); - - static::assertTrue($cart->has($productId)); - - $orderId = $cartService->order($cart, $salesChannelContext, new RequestDataBag()); - - /** @var EntityRepository $orderRepository */ - $orderRepository = $this->getContainer()->get('order.repository'); - - /** @var OrderEntity $order */ - $order = $orderRepository->search(new Criteria([$orderId]), $salesChannelContext->getContext())->first(); - - static::assertEquals($order->getLanguageId(), $this->getDeDeLanguageId()); - } - - public function testOrderCartEn(): void - { - $context = Context::createDefaultContext(); - $customerId = $this->createCustomer($context); - - $cartService = $this->getContainer()->get(CartService::class); - - $options = [ - SalesChannelContextService::LANGUAGE_ID => Defaults::LANGUAGE_SYSTEM, - SalesChannelContextService::CUSTOMER_ID => $customerId, - SalesChannelContextService::SHIPPING_METHOD_ID => $this->createShippingMethod(), - ]; - - $salesChannelContext = $this->getContainer()->get(SalesChannelContextFactory::class) - ->create(Uuid::randomHex(), TestDefaults::SALES_CHANNEL, $options); - - $productId = Uuid::randomHex(); - $product = [ - 'id' => $productId, - 'productNumber' => $productId, - 'name' => [ - 'de-DE' => 'test', - 'en-GB' => 'test', - ], - 'stock' => 10, - 'price' => [ - ['currencyId' => Defaults::CURRENCY, 'gross' => 100, 'net' => 100, 'linked' => false], - ], - 'active' => true, - 'visibilities' => [ - ['salesChannelId' => $salesChannelContext->getSalesChannel()->getId(), 'visibility' => ProductVisibilityDefinition::VISIBILITY_ALL], - ], - 'tax' => ['id' => Uuid::randomHex(), 'name' => 'test', 'taxRate' => 18], - 'manufacturer' => [ - 'name' => [ - 'de-DE' => 'test', - 'en-GB' => 'test', - ], - ], - ]; - - $this->getContainer()->get('product.repository') - ->create([$product], $salesChannelContext->getContext()); - $this->addTaxDataToSalesChannel($salesChannelContext, $product['tax']); - - $lineItem = (new ProductLineItemFactory(new PriceDefinitionFactory()))->create(['id' => $productId, 'referencedId' => $productId], $salesChannelContext); - - $cart = $cartService->getCart($salesChannelContext->getToken(), $salesChannelContext); - - $cart = $cartService->add($cart, $lineItem, $salesChannelContext); - - static::assertTrue($cart->has($productId)); - - $orderId = $cartService->order($cart, $salesChannelContext, new RequestDataBag()); - - /** @var EntityRepository $orderRepository */ - $orderRepository = $this->getContainer()->get('order.repository'); - - /** @var OrderEntity $order */ - $order = $orderRepository->search(new Criteria([$orderId]), $salesChannelContext->getContext())->first(); - - static::assertEquals($order->getLanguageId(), Defaults::LANGUAGE_SYSTEM); - } - - private function createShippingMethod(): string - { - $rule = [ - 'id' => Uuid::randomHex(), - 'name' => 'test', - 'priority' => 1, - 'conditions' => [ - ['type' => (new AlwaysValidRule())->getName()], - ], - ]; - - $this->getContainer()->get('rule.repository') - ->create([$rule], Context::createDefaultContext()); - - $shipping = [ - 'id' => Uuid::randomHex(), - 'name' => 'test', - 'technicalName' => 'shipping_test', - 'active' => true, - 'prices' => [ - [ - 'ruleId' => null, - 'quantityStart' => 0, - 'currencyPrice' => [ - ['currencyId' => Defaults::CURRENCY, 'gross' => 10, 'net' => 10, 'linked' => false], - ], - ], - ], - 'availabilityRuleId' => $rule['id'], - 'deliveryTimeId' => $this->getContainer()->get(Connection::class)->fetchOne('SELECT LOWER(HEX(id)) FROm delivery_time LIMIT 1'), - 'salesChannels' => [['id' => TestDefaults::SALES_CHANNEL]], - ]; - - $this->getContainer()->get('shipping_method.repository') - ->create([$shipping], Context::createDefaultContext()); - - return $shipping['id']; - } - - private function createOrder(string $customerId, Context $context): string - { - $orderId = Uuid::randomHex(); - $stateId = $this->getContainer()->get(InitialStateIdLoader::class)->get(OrderStates::STATE_MACHINE); - $billingAddressId = Uuid::randomHex(); - - $order = [ - 'id' => $orderId, - 'itemRounding' => json_decode(json_encode(new CashRoundingConfig(2, 0.01, true), \JSON_THROW_ON_ERROR), true, 512, \JSON_THROW_ON_ERROR), - 'totalRounding' => json_decode(json_encode(new CashRoundingConfig(2, 0.01, true), \JSON_THROW_ON_ERROR), true, 512, \JSON_THROW_ON_ERROR), - 'orderNumber' => Uuid::randomHex(), - 'orderDateTime' => (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT), - 'price' => new CartPrice(10, 10, 10, new CalculatedTaxCollection(), new TaxRuleCollection(), CartPrice::TAX_STATE_NET), - 'shippingCosts' => new CalculatedPrice(10, 10, new CalculatedTaxCollection(), new TaxRuleCollection()), - 'orderCustomer' => [ - 'customerId' => $customerId, - 'email' => 'test@example.com', - 'salutationId' => $this->getValidSalutationId(), - 'firstName' => 'Max', - 'lastName' => 'Mustermann', - ], - 'stateId' => $stateId, - 'paymentMethodId' => $this->getValidPaymentMethodId(), - 'currencyId' => Defaults::CURRENCY, - 'currencyFactor' => 1.0, - 'salesChannelId' => TestDefaults::SALES_CHANNEL, - 'billingAddressId' => $billingAddressId, - 'addresses' => [ - [ - 'id' => $billingAddressId, - 'salutationId' => $this->getValidSalutationId(), - 'firstName' => 'Max', - 'lastName' => 'Mustermann', - 'street' => 'Ebbinghoff 10', - 'zipcode' => '48624', - 'city' => 'Schöppingen', - 'countryId' => $this->getValidCountryId(), - ], - ], - 'lineItems' => [], - 'deliveries' => [], - 'context' => '{}', - 'payload' => '{}', - ]; - - $this->orderRepository->upsert([$order], $context); - - return $orderId; - } - - private function createCustomer(Context $context): string - { - $customerId = Uuid::randomHex(); - $addressId = Uuid::randomHex(); - $this->addCountriesToSalesChannel(); - - $customer = [ - 'id' => $customerId, - 'customerNumber' => '1337', - 'salutationId' => $this->getValidSalutationId(), - 'firstName' => 'Max', - 'lastName' => 'Mustermann', - 'email' => Uuid::randomHex() . '@example.com', - 'password' => TestDefaults::HASHED_PASSWORD, - 'defaultPaymentMethodId' => $this->getValidPaymentMethodId(), - 'groupId' => TestDefaults::FALLBACK_CUSTOMER_GROUP, - 'salesChannelId' => TestDefaults::SALES_CHANNEL, - 'defaultBillingAddressId' => $addressId, - 'defaultShippingAddressId' => $addressId, - 'addresses' => [ - [ - 'id' => $addressId, - 'customerId' => $customerId, - 'countryId' => $this->getValidCountryId(), - 'salutationId' => $this->getValidSalutationId(), - 'firstName' => 'Max', - 'lastName' => 'Mustermann', - 'street' => 'Ebbinghoff 10', - 'zipcode' => '48624', - 'city' => 'Schöppingen', - ], - ], - ]; - - $this->customerRepository->upsert([$customer], $context); - - return $customerId; - } -}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-3867-jc5c-66qfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-22407ghsaADVISORY
- github.com/shopware/core/commit/78142489264f9262eaaa436ba036df40026a06beghsaWEB
- github.com/shopware/shopware/commit/fb25e24ca51650009ffa2520f1e67b48b911354aghsaWEB
- github.com/shopware/shopware/security/advisories/GHSA-3867-jc5c-66qfghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.