vendor/shopware/core/Framework/Api/Acl/AclWriteValidator.php line 43

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Api\Acl;
  3. use Shopware\Core\Framework\Api\Acl\Event\CommandAclValidationEvent;
  4. use Shopware\Core\Framework\Api\Acl\Role\AclRoleDefinition;
  5. use Shopware\Core\Framework\Api\Context\AdminApiSource;
  6. use Shopware\Core\Framework\Api\Context\AdminSalesChannelApiSource;
  7. use Shopware\Core\Framework\Api\Exception\MissingPrivilegeException;
  8. use Shopware\Core\Framework\Context;
  9. use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  12. use Shopware\Core\Framework\Log\Package;
  13. use Shopware\Core\Framework\Uuid\Uuid;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  16. /**
  17. * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  18. */
  19. #[Package('system-settings')]
  20. class AclWriteValidator implements EventSubscriberInterface
  21. {
  22. private EventDispatcherInterface $eventDispatcher;
  23. /**
  24. * @internal
  25. */
  26. public function __construct(EventDispatcherInterface $eventDispatcher)
  27. {
  28. $this->eventDispatcher = $eventDispatcher;
  29. }
  30. /**
  31. * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  32. */
  33. public static function getSubscribedEvents()
  34. {
  35. return [PreWriteValidationEvent::class => 'preValidate'];
  36. }
  37. public function preValidate(PreWriteValidationEvent $event): void
  38. {
  39. $context = $event->getContext();
  40. $source = $event->getContext()->getSource();
  41. if ($source instanceof AdminSalesChannelApiSource) {
  42. $context = $source->getOriginalContext();
  43. $source = $context->getSource();
  44. }
  45. if ($context->getScope() === Context::SYSTEM_SCOPE || !$source instanceof AdminApiSource || $source->isAdmin()) {
  46. return;
  47. }
  48. $commands = $event->getCommands();
  49. $missingPrivileges = [];
  50. foreach ($commands as $command) {
  51. $resource = $command->getDefinition()->getEntityName();
  52. $privilege = $command->getPrivilege();
  53. if ($privilege === null) {
  54. continue;
  55. }
  56. if (is_subclass_of($command->getDefinition(), EntityTranslationDefinition::class)) {
  57. $resource = $command->getDefinition()->getParentDefinition()->getEntityName();
  58. if ($privilege !== AclRoleDefinition::PRIVILEGE_DELETE) {
  59. $privilege = $this->getPrivilegeForParentWriteOperation($command, $commands);
  60. }
  61. }
  62. if (!$source->isAllowed($resource . ':' . $privilege)) {
  63. $missingPrivileges[] = $resource . ':' . $privilege;
  64. }
  65. $event = new CommandAclValidationEvent($missingPrivileges, $source, $command);
  66. $this->eventDispatcher->dispatch($event);
  67. $missingPrivileges = $event->getMissingPrivileges();
  68. }
  69. $this->tryToThrow($missingPrivileges);
  70. }
  71. /**
  72. * @param list<string> $missingPrivileges
  73. */
  74. private function tryToThrow(array $missingPrivileges): void
  75. {
  76. if (!empty($missingPrivileges)) {
  77. throw new MissingPrivilegeException($missingPrivileges);
  78. }
  79. }
  80. /**
  81. * @param WriteCommand[] $commands
  82. */
  83. private function getPrivilegeForParentWriteOperation(WriteCommand $command, array $commands): string
  84. {
  85. $pathSuffix = '/translations/' . Uuid::fromBytesToHex($command->getPrimaryKey()['language_id']);
  86. $parentCommandPath = str_replace($pathSuffix, '', $command->getPath());
  87. $parentCommand = $this->findCommandByPath($parentCommandPath, $commands);
  88. // writes to translation need privilege from parent command
  89. // if we update e.g. a product and add translations for a new language
  90. // the writeCommand on the translation would be an insert
  91. if ($parentCommand) {
  92. return (string) $parentCommand->getPrivilege();
  93. }
  94. // if we don't have a parentCommand it must be a update,
  95. // because the parentEntity must already exist
  96. return AclRoleDefinition::PRIVILEGE_UPDATE;
  97. }
  98. /**
  99. * @param WriteCommand[] $commands
  100. */
  101. private function findCommandByPath(string $commandPath, array $commands): ?WriteCommand
  102. {
  103. foreach ($commands as $command) {
  104. if ($command->getPath() === $commandPath) {
  105. return $command;
  106. }
  107. }
  108. return null;
  109. }
  110. }